deal.II version GIT relicensing-2173-gae8fc9d14b 2024-11-24 06:40:00+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
step-63.h
Go to the documentation of this file.
1
519 *   ParameterHandler prm;
520 *  
521 *   prm.declare_entry("Epsilon",
522 *   "0.005",
523 *   Patterns::Double(0),
524 *   "Diffusion parameter");
525 *  
526 *   prm.declare_entry("Fe degree",
527 *   "1",
528 *   Patterns::Integer(1),
529 *   "Finite Element degree");
530 *   prm.declare_entry("Smoother type",
531 *   "block SOR",
532 *   Patterns::Selection("SOR|Jacobi|block SOR|block Jacobi"),
533 *   "Select smoother: SOR|Jacobi|block SOR|block Jacobi");
534 *   prm.declare_entry("Smoothing steps",
535 *   "2",
536 *   Patterns::Integer(1),
537 *   "Number of smoothing steps");
538 *   prm.declare_entry(
539 *   "DoF renumbering",
540 *   "downstream",
541 *   Patterns::Selection("none|downstream|upstream|random"),
542 *   "Select DoF renumbering: none|downstream|upstream|random");
543 *   prm.declare_entry("With streamline diffusion",
544 *   "true",
545 *   Patterns::Bool(),
546 *   "Enable streamline diffusion stabilization: true|false");
547 *   prm.declare_entry("Output",
548 *   "true",
549 *   Patterns::Bool(),
550 *   "Generate graphical output: true|false");
551 *  
552 *   /* ...and then try to read their values from the input file: */
553 *   if (prm_filename.empty())
554 *   {
555 *   prm.print_parameters(std::cout, ParameterHandler::PRM);
556 *   AssertThrow(
557 *   false, ExcMessage("Please pass a .prm file as the first argument!"));
558 *   }
559 *  
560 *   prm.parse_input(prm_filename);
561 *  
562 *   epsilon = prm.get_double("Epsilon");
563 *   fe_degree = prm.get_integer("Fe degree");
564 *   smoother_type = prm.get("Smoother type");
565 *   smoothing_steps = prm.get_integer("Smoothing steps");
566 *  
567 *   const std::string renumbering = prm.get("DoF renumbering");
568 *   if (renumbering == "none")
569 *   dof_renumbering = DoFRenumberingStrategy::none;
570 *   else if (renumbering == "downstream")
571 *   dof_renumbering = DoFRenumberingStrategy::downstream;
572 *   else if (renumbering == "upstream")
573 *   dof_renumbering = DoFRenumberingStrategy::upstream;
574 *   else if (renumbering == "random")
575 *   dof_renumbering = DoFRenumberingStrategy::random;
576 *   else
577 *   AssertThrow(false,
578 *   ExcMessage("The <DoF renumbering> parameter has "
579 *   "an invalid value."));
580 *  
581 *   with_streamline_diffusion = prm.get_bool("With streamline diffusion");
582 *   output = prm.get_bool("Output");
583 *   }
584 *  
585 *  
586 * @endcode
587 *
588 *
589 * <a name="step_63-Cellpermutations"></a>
590 * <h3>Cell permutations</h3>
591 *
592
593 *
594 * The ordering in which cells and degrees of freedom are traversed
595 * will play a role in the speed of convergence for multiplicative
596 * methods. Here we define functions which return a specific ordering
597 * of cells to be used by the block smoothers.
598 *
599
600 *
601 * For each type of cell ordering, we define a function for the
602 * active mesh and one for a level mesh (i.e., for the cells at one
603 * level of a multigrid hierarchy). While the only reordering
604 * necessary for solving the system will be on the level meshes, we
605 * include the active reordering for visualization purposes in
606 * output_results().
607 *
608
609 *
610 * For the two downstream ordering functions, we first create an
611 * array with all of the relevant cells that we then sort in
612 * downstream direction using a "comparator" object. The output of
613 * the functions is then simply an array of the indices of the cells
614 * in the just computed order.
615 *
616 * @code
617 *   template <int dim>
618 *   std::vector<unsigned int>
619 *   create_downstream_cell_ordering(const DoFHandler<dim> &dof_handler,
620 *   const Tensor<1, dim> direction,
621 *   const unsigned int level)
622 *   {
623 *   std::vector<typename DoFHandler<dim>::level_cell_iterator> ordered_cells;
624 *   ordered_cells.reserve(dof_handler.get_triangulation().n_cells(level));
625 *   for (const auto &cell : dof_handler.cell_iterators_on_level(level))
626 *   ordered_cells.push_back(cell);
627 *  
628 *   const DoFRenumbering::
629 *   CompareDownstream<typename DoFHandler<dim>::level_cell_iterator, dim>
630 *   comparator(direction);
631 *   std::sort(ordered_cells.begin(), ordered_cells.end(), comparator);
632 *  
633 *   std::vector<unsigned> ordered_indices;
634 *   ordered_indices.reserve(dof_handler.get_triangulation().n_cells(level));
635 *  
636 *   for (const auto &cell : ordered_cells)
637 *   ordered_indices.push_back(cell->index());
638 *  
639 *   return ordered_indices;
640 *   }
641 *  
642 *  
643 *  
644 *   template <int dim>
645 *   std::vector<unsigned int>
646 *   create_downstream_cell_ordering(const DoFHandler<dim> &dof_handler,
647 *   const Tensor<1, dim> direction)
648 *   {
649 *   std::vector<typename DoFHandler<dim>::active_cell_iterator> ordered_cells;
650 *   ordered_cells.reserve(dof_handler.get_triangulation().n_active_cells());
651 *   for (const auto &cell : dof_handler.active_cell_iterators())
652 *   ordered_cells.push_back(cell);
653 *  
654 *   const DoFRenumbering::
655 *   CompareDownstream<typename DoFHandler<dim>::active_cell_iterator, dim>
656 *   comparator(direction);
657 *   std::sort(ordered_cells.begin(), ordered_cells.end(), comparator);
658 *  
659 *   std::vector<unsigned int> ordered_indices;
660 *   ordered_indices.reserve(dof_handler.get_triangulation().n_active_cells());
661 *  
662 *   for (const auto &cell : ordered_cells)
663 *   ordered_indices.push_back(cell->index());
664 *  
665 *   return ordered_indices;
666 *   }
667 *  
668 *  
669 * @endcode
670 *
671 * The functions that produce a random ordering are similar in
672 * spirit in that they first put information about all cells into an
673 * array. But then, instead of sorting them, they shuffle the
674 * elements randomly using the facilities C++ offers to generate
675 * random numbers. The way this is done is by iterating over all
676 * elements of the array, drawing a random number for another
677 * element before that, and then exchanging these elements. The
678 * result is a random shuffle of the elements of the array.
679 *
680 * @code
681 *   template <int dim>
682 *   std::vector<unsigned int>
683 *   create_random_cell_ordering(const DoFHandler<dim> &dof_handler,
684 *   const unsigned int level)
685 *   {
686 *   std::vector<unsigned int> ordered_cells;
687 *   ordered_cells.reserve(dof_handler.get_triangulation().n_cells(level));
688 *   for (const auto &cell : dof_handler.cell_iterators_on_level(level))
689 *   ordered_cells.push_back(cell->index());
690 *  
691 *   std::mt19937 random_number_generator;
692 *   std::shuffle(ordered_cells.begin(),
693 *   ordered_cells.end(),
694 *   random_number_generator);
695 *  
696 *   return ordered_cells;
697 *   }
698 *  
699 *  
700 *  
701 *   template <int dim>
702 *   std::vector<unsigned int>
703 *   create_random_cell_ordering(const DoFHandler<dim> &dof_handler)
704 *   {
705 *   std::vector<unsigned int> ordered_cells;
706 *   ordered_cells.reserve(dof_handler.get_triangulation().n_active_cells());
707 *   for (const auto &cell : dof_handler.active_cell_iterators())
708 *   ordered_cells.push_back(cell->index());
709 *  
710 *   std::mt19937 random_number_generator;
711 *   std::shuffle(ordered_cells.begin(),
712 *   ordered_cells.end(),
713 *   random_number_generator);
714 *  
715 *   return ordered_cells;
716 *   }
717 *  
718 *  
719 * @endcode
720 *
721 *
722 * <a name="step_63-Righthandsideandboundaryvalues"></a>
723 * <h3>Right-hand side and boundary values</h3>
724 *
725
726 *
727 * The problem solved in this tutorial is an adaptation of Ex. 3.1.3 found
728 * on pg. 118 of <a
729 * href="https://global.oup.com/academic/product/finite-elements-and-fast-iterative-solvers-9780199678808">
730 * Finite Elements and Fast Iterative Solvers: with Applications in
731 * Incompressible Fluid Dynamics by Elman, Silvester, and Wathen</a>. The
732 * main difference being that we add a hole in the center of our domain with
733 * zero Dirichlet boundary conditions.
734 *
735
736 *
737 * For a complete description, we need classes that implement the
738 * zero right-hand side first (we could of course have just used
740 *
741 * @code
742 *   template <int dim>
743 *   class RightHandSide : public Function<dim>
744 *   {
745 *   public:
746 *   virtual double value(const Point<dim> &p,
747 *   const unsigned int component = 0) const override;
748 *  
749 *   virtual void value_list(const std::vector<Point<dim>> &points,
750 *   std::vector<double> &values,
751 *   const unsigned int component = 0) const override;
752 *   };
753 *  
754 *  
755 *  
756 *   template <int dim>
757 *   double RightHandSide<dim>::value(const Point<dim> &,
758 *   const unsigned int component) const
759 *   {
760 *   Assert(component == 0, ExcIndexRange(component, 0, 1));
761 *   (void)component;
762 *  
763 *   return 0.0;
764 *   }
765 *  
766 *  
767 *  
768 *   template <int dim>
769 *   void RightHandSide<dim>::value_list(const std::vector<Point<dim>> &points,
770 *   std::vector<double> &values,
771 *   const unsigned int component) const
772 *   {
773 *   AssertDimension(values.size(), points.size());
774 *  
775 *   for (unsigned int i = 0; i < points.size(); ++i)
776 *   values[i] = RightHandSide<dim>::value(points[i], component);
777 *   }
778 *  
779 *  
780 * @endcode
781 *
782 * We also have Dirichlet boundary conditions. On a connected portion of the
783 * outer, square boundary we set the value to 1, and we set the value to 0
784 * everywhere else (including the inner, circular boundary):
785 *
786 * @code
787 *   template <int dim>
788 *   class BoundaryValues : public Function<dim>
789 *   {
790 *   public:
791 *   virtual double value(const Point<dim> &p,
792 *   const unsigned int component = 0) const override;
793 *  
794 *   virtual void value_list(const std::vector<Point<dim>> &points,
795 *   std::vector<double> &values,
796 *   const unsigned int component = 0) const override;
797 *   };
798 *  
799 *  
800 *  
801 *   template <int dim>
802 *   double BoundaryValues<dim>::value(const Point<dim> &p,
803 *   const unsigned int component) const
804 *   {
805 *   Assert(component == 0, ExcIndexRange(component, 0, 1));
806 *   (void)component;
807 *  
808 * @endcode
809 *
810 * Set boundary to 1 if @f$x=1@f$, or if @f$x>0.5@f$ and @f$y=-1@f$.
811 *
812 * @code
813 *   if (std::fabs(p[0] - 1) < 1e-8 ||
814 *   (std::fabs(p[1] + 1) < 1e-8 && p[0] >= 0.5))
815 *   {
816 *   return 1.0;
817 *   }
818 *   else
819 *   {
820 *   return 0.0;
821 *   }
822 *   }
823 *  
824 *  
825 *  
826 *   template <int dim>
827 *   void BoundaryValues<dim>::value_list(const std::vector<Point<dim>> &points,
828 *   std::vector<double> &values,
829 *   const unsigned int component) const
830 *   {
831 *   AssertDimension(values.size(), points.size());
832 *  
833 *   for (unsigned int i = 0; i < points.size(); ++i)
834 *   values[i] = BoundaryValues<dim>::value(points[i], component);
835 *   }
836 *  
837 *  
838 *  
839 * @endcode
840 *
841 *
842 * <a name="step_63-Streamlinediffusionimplementation"></a>
843 * <h3>Streamline diffusion implementation</h3>
844 *
845
846 *
847 * The streamline diffusion method has a stabilization constant that
848 * we need to be able to compute. The choice of how this parameter
849 * is computed is taken from <a
850 * href="https://link.springer.com/chapter/10.1007/978-3-540-34288-5_27">On
851 * Discontinuity-Capturing Methods for Convection-Diffusion
852 * Equations by Volker John and Petr Knobloch</a>.
853 *
854 * @code
855 *   template <int dim>
856 *   double compute_stabilization_delta(const double hk,
857 *   const double eps,
858 *   const Tensor<1, dim> dir,
859 *   const double pk)
860 *   {
861 *   const double Peclet = dir.norm() * hk / (2.0 * eps * pk);
862 *   const double coth =
863 *   (1.0 + std::exp(-2.0 * Peclet)) / (1.0 - std::exp(-2.0 * Peclet));
864 *  
865 *   return hk / (2.0 * dir.norm() * pk) * (coth - 1.0 / Peclet);
866 *   }
867 *  
868 *  
869 * @endcode
870 *
871 *
872 * <a name="step_63-codeAdvectionProblemcodeclass"></a>
873 * <h3><code>AdvectionProblem</code> class</h3>
874 *
875
876 *
877 * This is the main class of the program, and should look very similar to
878 * @ref step_16 "step-16". The major difference is that, since we are defining our multigrid
879 * smoother at runtime, we choose to define a function `create_smoother()` and
880 * a class object `mg_smoother` which is a `std::unique_ptr` to a smoother
881 * that is derived from MGSmoother. Note that for smoothers derived from
882 * RelaxationBlock, we must include a `smoother_data` object for each level.
883 * This will contain information about the cell ordering and the method of
884 * inverting cell matrices.
885 *
886
887 *
888 *
889 * @code
890 *   template <int dim>
891 *   class AdvectionProblem
892 *   {
893 *   public:
894 *   AdvectionProblem(const Settings &settings);
895 *   void run();
896 *  
897 *   private:
898 *   void setup_system();
899 *  
900 *   template <class IteratorType>
901 *   void assemble_cell(const IteratorType &cell,
902 *   ScratchData<dim> &scratch_data,
903 *   CopyData &copy_data);
904 *   void assemble_system_and_multigrid();
905 *  
906 *   void setup_smoother();
907 *  
908 *   void solve();
909 *   void refine_grid();
910 *   void output_results(const unsigned int cycle) const;
911 *  
913 *   DoFHandler<dim> dof_handler;
914 *  
915 *   const FE_Q<dim> fe;
916 *   const MappingQ<dim> mapping;
917 *  
918 *   AffineConstraints<double> constraints;
919 *  
920 *   SparsityPattern sparsity_pattern;
921 *   SparseMatrix<double> system_matrix;
922 *  
923 *   Vector<double> solution;
924 *   Vector<double> system_rhs;
925 *  
926 *   MGLevelObject<SparsityPattern> mg_sparsity_patterns;
927 *   MGLevelObject<SparsityPattern> mg_interface_sparsity_patterns;
928 *  
929 *   MGLevelObject<SparseMatrix<double>> mg_matrices;
930 *   MGLevelObject<SparseMatrix<double>> mg_interface_in;
931 *   MGLevelObject<SparseMatrix<double>> mg_interface_out;
932 *  
933 *   mg::Matrix<Vector<double>> mg_matrix;
934 *   mg::Matrix<Vector<double>> mg_interface_matrix_in;
935 *   mg::Matrix<Vector<double>> mg_interface_matrix_out;
936 *  
937 *   std::unique_ptr<MGSmoother<Vector<double>>> mg_smoother;
938 *  
939 *   using SmootherType =
941 *   using SmootherAdditionalDataType = SmootherType::AdditionalData;
943 *  
944 *   MGConstrainedDoFs mg_constrained_dofs;
945 *  
946 *   Tensor<1, dim> advection_direction;
947 *  
948 *   const Settings settings;
949 *   };
950 *  
951 *  
952 *  
953 *   template <int dim>
954 *   AdvectionProblem<dim>::AdvectionProblem(const Settings &settings)
956 *   , dof_handler(triangulation)
957 *   , fe(settings.fe_degree)
958 *   , mapping(settings.fe_degree)
959 *   , settings(settings)
960 *   {
961 *   advection_direction[0] = -std::sin(numbers::PI / 6.0);
962 *   if (dim >= 2)
963 *   advection_direction[1] = std::cos(numbers::PI / 6.0);
964 *   if (dim >= 3)
965 *   AssertThrow(false, ExcNotImplemented());
966 *   }
967 *  
968 *  
969 * @endcode
970 *
971 *
972 * <a name="step_63-codeAdvectionProblemsetup_systemcode"></a>
973 * <h4><code>AdvectionProblem::setup_system()</code></h4>
974 *
975
976 *
977 * Here we first set up the DoFHandler, AffineConstraints, and
978 * SparsityPattern objects for both active and multigrid level meshes.
979 *
980
981 *
982 * We could renumber the active DoFs with the DoFRenumbering class,
983 * but the smoothers only act on multigrid levels and as such, this
984 * would not matter for the computations. Instead, we will renumber the
985 * DoFs on each multigrid level below.
986 *
987 * @code
988 *   template <int dim>
989 *   void AdvectionProblem<dim>::setup_system()
990 *   {
991 *   const unsigned int n_levels = triangulation.n_levels();
992 *  
993 *   dof_handler.distribute_dofs(fe);
994 *  
995 *   solution.reinit(dof_handler.n_dofs());
996 *   system_rhs.reinit(dof_handler.n_dofs());
997 *  
998 *   constraints.clear();
999 *   DoFTools::make_hanging_node_constraints(dof_handler, constraints);
1000 *  
1002 *   mapping, dof_handler, 0, BoundaryValues<dim>(), constraints);
1004 *   mapping, dof_handler, 1, BoundaryValues<dim>(), constraints);
1005 *   constraints.close();
1006 *  
1007 *   DynamicSparsityPattern dsp(dof_handler.n_dofs());
1008 *   DoFTools::make_sparsity_pattern(dof_handler,
1009 *   dsp,
1010 *   constraints,
1011 *   /*keep_constrained_dofs = */ false);
1012 *  
1013 *   sparsity_pattern.copy_from(dsp);
1014 *   system_matrix.reinit(sparsity_pattern);
1015 *  
1016 *   dof_handler.distribute_mg_dofs();
1017 *  
1018 * @endcode
1019 *
1020 * Having enumerated the global degrees of freedom as well as (in
1021 * the last line above) the level degrees of freedom, let us
1022 * renumber the level degrees of freedom to get a better smoother
1023 * as explained in the introduction. The first block below
1024 * renumbers DoFs on each level in downstream or upstream
1025 * direction if needed. This is only necessary for point smoothers
1026 * (SOR and Jacobi) as the block smoothers operate on cells (see
1027 * `create_smoother()`). The blocks below then also implement
1028 * random numbering.
1029 *
1030 * @code
1031 *   if (settings.smoother_type == "SOR" || settings.smoother_type == "Jacobi")
1032 *   {
1033 *   if (settings.dof_renumbering ==
1034 *   Settings::DoFRenumberingStrategy::downstream ||
1035 *   settings.dof_renumbering ==
1036 *   Settings::DoFRenumberingStrategy::upstream)
1037 *   {
1038 *   const Tensor<1, dim> direction =
1039 *   (settings.dof_renumbering ==
1040 *   Settings::DoFRenumberingStrategy::upstream ?
1041 *   -1.0 :
1042 *   1.0) *
1043 *   advection_direction;
1044 *  
1045 *   for (unsigned int level = 0; level < n_levels; ++level)
1046 *   DoFRenumbering::downstream(dof_handler,
1047 *   level,
1048 *   direction,
1049 *   /*dof_wise_renumbering = */ true);
1050 *   }
1051 *   else if (settings.dof_renumbering ==
1052 *   Settings::DoFRenumberingStrategy::random)
1053 *   {
1054 *   for (unsigned int level = 0; level < n_levels; ++level)
1055 *   DoFRenumbering::random(dof_handler, level);
1056 *   }
1057 *   else
1059 *   }
1060 *  
1061 * @endcode
1062 *
1063 * The rest of the function just sets up data structures. The last
1064 * lines of the code below is unlike the other GMG tutorials, as
1065 * it sets up both the interface in and out matrices. We need this
1066 * since our problem is non-symmetric.
1067 *
1068 * @code
1069 *   mg_constrained_dofs.clear();
1070 *   mg_constrained_dofs.initialize(dof_handler);
1071 *  
1072 *   mg_constrained_dofs.make_zero_boundary_constraints(dof_handler, {0, 1});
1073 *  
1074 *   mg_matrices.resize(0, n_levels - 1);
1075 *   mg_matrices.clear_elements();
1076 *   mg_interface_in.resize(0, n_levels - 1);
1077 *   mg_interface_in.clear_elements();
1078 *   mg_interface_out.resize(0, n_levels - 1);
1079 *   mg_interface_out.clear_elements();
1080 *   mg_sparsity_patterns.resize(0, n_levels - 1);
1081 *   mg_interface_sparsity_patterns.resize(0, n_levels - 1);
1082 *  
1083 *   for (unsigned int level = 0; level < n_levels; ++level)
1084 *   {
1085 *   {
1086 *   DynamicSparsityPattern dsp(dof_handler.n_dofs(level),
1087 *   dof_handler.n_dofs(level));
1088 *   MGTools::make_sparsity_pattern(dof_handler, dsp, level);
1089 *   mg_sparsity_patterns[level].copy_from(dsp);
1090 *   mg_matrices[level].reinit(mg_sparsity_patterns[level]);
1091 *   }
1092 *   {
1093 *   DynamicSparsityPattern dsp(dof_handler.n_dofs(level),
1094 *   dof_handler.n_dofs(level));
1096 *   mg_constrained_dofs,
1097 *   dsp,
1098 *   level);
1099 *   mg_interface_sparsity_patterns[level].copy_from(dsp);
1100 *  
1101 *   mg_interface_in[level].reinit(mg_interface_sparsity_patterns[level]);
1102 *   mg_interface_out[level].reinit(mg_interface_sparsity_patterns[level]);
1103 *   }
1104 *   }
1105 *   }
1106 *  
1107 *  
1108 * @endcode
1109 *
1110 *
1111 * <a name="step_63-codeAdvectionProblemassemble_cellcode"></a>
1112 * <h4><code>AdvectionProblem::assemble_cell()</code></h4>
1113 *
1114
1115 *
1116 * Here we define the assembly of the linear system on each cell to
1117 * be used by the mesh_loop() function below. This one function
1118 * assembles the cell matrix for either an active or a level cell
1119 * (whatever it is passed as its first argument), and only assembles
1120 * a right-hand side if called with an active cell.
1121 *
1122
1123 *
1124 *
1125 * @code
1126 *   template <int dim>
1127 *   template <class IteratorType>
1128 *   void AdvectionProblem<dim>::assemble_cell(const IteratorType &cell,
1129 *   ScratchData<dim> &scratch_data,
1130 *   CopyData &copy_data)
1131 *   {
1132 *   copy_data.level = cell->level();
1133 *  
1134 *   const unsigned int dofs_per_cell =
1135 *   scratch_data.fe_values.get_fe().n_dofs_per_cell();
1136 *   copy_data.dofs_per_cell = dofs_per_cell;
1137 *   copy_data.cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
1138 *  
1139 *   const unsigned int n_q_points =
1140 *   scratch_data.fe_values.get_quadrature().size();
1141 *  
1142 *   if (cell->is_level_cell() == false)
1143 *   copy_data.cell_rhs.reinit(dofs_per_cell);
1144 *  
1145 *   copy_data.local_dof_indices.resize(dofs_per_cell);
1146 *   cell->get_active_or_mg_dof_indices(copy_data.local_dof_indices);
1147 *  
1148 *   scratch_data.fe_values.reinit(cell);
1149 *  
1150 *   RightHandSide<dim> right_hand_side;
1151 *   std::vector<double> rhs_values(n_q_points);
1152 *  
1153 *   right_hand_side.value_list(scratch_data.fe_values.get_quadrature_points(),
1154 *   rhs_values);
1155 *  
1156 * @endcode
1157 *
1158 * If we are using streamline diffusion we must add its contribution
1159 * to both the cell matrix and the cell right-hand side. If we are not
1160 * using streamline diffusion, setting @f$\delta=0@f$ negates this contribution
1161 * below and we are left with the standard, Galerkin finite element
1162 * assembly.
1163 *
1164 * @code
1165 *   const double delta = (settings.with_streamline_diffusion ?
1166 *   compute_stabilization_delta(cell->diameter(),
1167 *   settings.epsilon,
1168 *   advection_direction,
1169 *   settings.fe_degree) :
1170 *   0.0);
1171 *  
1172 *   for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
1173 *   for (unsigned int i = 0; i < dofs_per_cell; ++i)
1174 *   {
1175 *   for (unsigned int j = 0; j < dofs_per_cell; ++j)
1176 *   {
1177 * @endcode
1178 *
1179 * The assembly of the local matrix has two parts. First
1180 * the Galerkin contribution:
1181 *
1182 * @code
1183 *   copy_data.cell_matrix(i, j) +=
1184 *   (settings.epsilon *
1185 *   scratch_data.fe_values.shape_grad(i, q_point) *
1186 *   scratch_data.fe_values.shape_grad(j, q_point) *
1187 *   scratch_data.fe_values.JxW(q_point)) +
1188 *   (scratch_data.fe_values.shape_value(i, q_point) *
1189 *   (advection_direction *
1190 *   scratch_data.fe_values.shape_grad(j, q_point)) *
1191 *   scratch_data.fe_values.JxW(q_point))
1192 * @endcode
1193 *
1194 * and then the streamline diffusion contribution:
1195 *
1196 * @code
1197 *   + delta *
1198 *   (advection_direction *
1199 *   scratch_data.fe_values.shape_grad(j, q_point)) *
1200 *   (advection_direction *
1201 *   scratch_data.fe_values.shape_grad(i, q_point)) *
1202 *   scratch_data.fe_values.JxW(q_point) -
1203 *   delta * settings.epsilon *
1204 *   trace(scratch_data.fe_values.shape_hessian(j, q_point)) *
1205 *   (advection_direction *
1206 *   scratch_data.fe_values.shape_grad(i, q_point)) *
1207 *   scratch_data.fe_values.JxW(q_point);
1208 *   }
1209 *   if (cell->is_level_cell() == false)
1210 *   {
1211 * @endcode
1212 *
1213 * The same applies to the right hand side. First the
1214 * Galerkin contribution:
1215 *
1216 * @code
1217 *   copy_data.cell_rhs(i) +=
1218 *   scratch_data.fe_values.shape_value(i, q_point) *
1219 *   rhs_values[q_point] * scratch_data.fe_values.JxW(q_point)
1220 * @endcode
1221 *
1222 * and then the streamline diffusion contribution:
1223 *
1224 * @code
1225 *   + delta * rhs_values[q_point] * advection_direction *
1226 *   scratch_data.fe_values.shape_grad(i, q_point) *
1227 *   scratch_data.fe_values.JxW(q_point);
1228 *   }
1229 *   }
1230 *   }
1231 *  
1232 *  
1233 * @endcode
1234 *
1235 *
1236 * <a name="step_63-codeAdvectionProblemassemble_system_and_multigridcode"></a>
1237 * <h4><code>AdvectionProblem::assemble_system_and_multigrid()</code></h4>
1238 *
1239
1240 *
1241 * Here we employ MeshWorker::mesh_loop() to go over cells and assemble the
1242 * system_matrix, system_rhs, and all mg_matrices for us.
1243 *
1244
1245 *
1246 *
1247 * @code
1248 *   template <int dim>
1249 *   void AdvectionProblem<dim>::assemble_system_and_multigrid()
1250 *   {
1251 *   const auto cell_worker_active =
1252 *   [&](const decltype(dof_handler.begin_active()) &cell,
1253 *   ScratchData<dim> &scratch_data,
1254 *   CopyData &copy_data) {
1255 *   this->assemble_cell(cell, scratch_data, copy_data);
1256 *   };
1257 *  
1258 *   const auto copier_active = [&](const CopyData &copy_data) {
1259 *   constraints.distribute_local_to_global(copy_data.cell_matrix,
1260 *   copy_data.cell_rhs,
1261 *   copy_data.local_dof_indices,
1262 *   system_matrix,
1263 *   system_rhs);
1264 *   };
1265 *  
1266 *  
1267 *   MeshWorker::mesh_loop(dof_handler.begin_active(),
1268 *   dof_handler.end(),
1269 *   cell_worker_active,
1270 *   copier_active,
1271 *   ScratchData<dim>(fe, fe.degree + 1),
1272 *   CopyData(),
1274 *  
1275 * @endcode
1276 *
1277 * Unlike the constraints for the active level, we choose to create
1278 * constraint objects for each multigrid level local to this function
1279 * since they are never needed elsewhere in the program.
1280 *
1281 * @code
1282 *   std::vector<AffineConstraints<double>> boundary_constraints(
1283 *   triangulation.n_global_levels());
1284 *   for (unsigned int level = 0; level < triangulation.n_global_levels();
1285 *   ++level)
1286 *   {
1287 *   boundary_constraints[level].reinit(
1288 *   dof_handler.locally_owned_mg_dofs(level),
1290 *  
1291 *   for (const types::global_dof_index dof_index :
1292 *   mg_constrained_dofs.get_refinement_edge_indices(level))
1293 *   boundary_constraints[level].constrain_dof_to_zero(dof_index);
1294 *   for (const types::global_dof_index dof_index :
1295 *   mg_constrained_dofs.get_boundary_indices(level))
1296 *   boundary_constraints[level].constrain_dof_to_zero(dof_index);
1297 *   boundary_constraints[level].close();
1298 *   }
1299 *  
1300 *   const auto cell_worker_mg =
1301 *   [&](const decltype(dof_handler.begin_mg()) &cell,
1302 *   ScratchData<dim> &scratch_data,
1303 *   CopyData &copy_data) {
1304 *   this->assemble_cell(cell, scratch_data, copy_data);
1305 *   };
1306 *  
1307 *   const auto copier_mg = [&](const CopyData &copy_data) {
1308 *   boundary_constraints[copy_data.level].distribute_local_to_global(
1309 *   copy_data.cell_matrix,
1310 *   copy_data.local_dof_indices,
1311 *   mg_matrices[copy_data.level]);
1312 *  
1313 * @endcode
1314 *
1315 * If @f$(i,j)@f$ is an `interface_out` dof pair, then @f$(j,i)@f$ is an
1316 * `interface_in` dof pair. Note: For `interface_in`, we load
1317 * the transpose of the interface entries, i.e., the entry for
1318 * dof pair @f$(j,i)@f$ is stored in `interface_in(i,j)`. This is an
1319 * optimization for the symmetric case which allows only one
1320 * matrix to be used when setting the edge_matrices in
1321 * solve(). Here, however, since our problem is non-symmetric,
1322 * we must store both `interface_in` and `interface_out`
1323 * matrices.
1324 *
1325 * @code
1326 *   for (unsigned int i = 0; i < copy_data.dofs_per_cell; ++i)
1327 *   for (unsigned int j = 0; j < copy_data.dofs_per_cell; ++j)
1328 *   if (mg_constrained_dofs.is_interface_matrix_entry(
1329 *   copy_data.level,
1330 *   copy_data.local_dof_indices[i],
1331 *   copy_data.local_dof_indices[j]))
1332 *   {
1333 *   mg_interface_out[copy_data.level].add(
1334 *   copy_data.local_dof_indices[i],
1335 *   copy_data.local_dof_indices[j],
1336 *   copy_data.cell_matrix(i, j));
1337 *   mg_interface_in[copy_data.level].add(
1338 *   copy_data.local_dof_indices[i],
1339 *   copy_data.local_dof_indices[j],
1340 *   copy_data.cell_matrix(j, i));
1341 *   }
1342 *   };
1343 *  
1344 *   MeshWorker::mesh_loop(dof_handler.begin_mg(),
1345 *   dof_handler.end_mg(),
1346 *   cell_worker_mg,
1347 *   copier_mg,
1348 *   ScratchData<dim>(fe, fe.degree + 1),
1349 *   CopyData(),
1351 *   }
1352 *  
1353 *  
1354 * @endcode
1355 *
1356 *
1357 * <a name="step_63-codeAdvectionProblemsetup_smoothercode"></a>
1358 * <h4><code>AdvectionProblem::setup_smoother()</code></h4>
1359 *
1360
1361 *
1362 * Next, we set up the smoother based on the settings in the `.prm` file. The
1363 * two options that are of significance is the number of pre- and
1364 * post-smoothing steps on each level of the multigrid v-cycle and the
1365 * relaxation parameter.
1366 *
1367
1368 *
1369 * Since multiplicative methods tend to be more powerful than additive method,
1370 * fewer smoothing steps are required to see convergence independent of mesh
1371 * size. The same holds for block smoothers over point smoothers. This is
1372 * reflected in the choice for the number of smoothing steps for each type of
1373 * smoother below.
1374 *
1375
1376 *
1377 * The relaxation parameter for point smoothers is chosen based on trial and
1378 * error, and reflects values necessary to keep the iteration counts in
1379 * the GMRES solve constant (or as close as possible) as we refine the mesh.
1380 * The two values given for both "Jacobi" and "SOR" in the `.prm` files are
1381 * for degree 1 and degree 3 finite elements. If the user wants to change to
1382 * another degree, they may need to adjust these numbers. For block smoothers,
1383 * this parameter has a more straightforward interpretation, namely that for
1384 * additive methods in 2d, a DoF can have a repeated contribution from up to 4
1385 * cells, therefore we must relax these methods by 0.25 to compensate. This is
1386 * not an issue for multiplicative methods as each cell's inverse application
1387 * carries new information to all its DoFs.
1388 *
1389
1390 *
1391 * Finally, as mentioned above, the point smoothers only operate on DoFs, and
1392 * the block smoothers on cells, so only the block smoothers need to be given
1393 * information regarding cell orderings. DoF ordering for point smoothers has
1394 * already been taken care of in `setup_system()`.
1395 *
1396
1397 *
1398 *
1399 * @code
1400 *   template <int dim>
1401 *   void AdvectionProblem<dim>::setup_smoother()
1402 *   {
1403 *   if (settings.smoother_type == "SOR")
1404 *   {
1405 *   using Smoother = PreconditionSOR<SparseMatrix<double>>;
1406 *  
1407 *   auto smoother =
1408 *   std::make_unique<MGSmootherPrecondition<SparseMatrix<double>,
1409 *   Smoother,
1410 *   Vector<double>>>();
1411 *   smoother->initialize(mg_matrices,
1412 *   Smoother::AdditionalData(fe.degree == 1 ? 1.0 :
1413 *   0.62));
1414 *   smoother->set_steps(settings.smoothing_steps);
1415 *   mg_smoother = std::move(smoother);
1416 *   }
1417 *   else if (settings.smoother_type == "Jacobi")
1418 *   {
1419 *   using Smoother = PreconditionJacobi<SparseMatrix<double>>;
1420 *   auto smoother =
1421 *   std::make_unique<MGSmootherPrecondition<SparseMatrix<double>,
1422 *   Smoother,
1423 *   Vector<double>>>();
1424 *   smoother->initialize(mg_matrices,
1425 *   Smoother::AdditionalData(fe.degree == 1 ? 0.6667 :
1426 *   0.47));
1427 *   smoother->set_steps(settings.smoothing_steps);
1428 *   mg_smoother = std::move(smoother);
1429 *   }
1430 *   else if (settings.smoother_type == "block SOR" ||
1431 *   settings.smoother_type == "block Jacobi")
1432 *   {
1433 *   smoother_data.resize(0, triangulation.n_levels() - 1);
1434 *  
1435 *   for (unsigned int level = 0; level < triangulation.n_levels(); ++level)
1436 *   {
1437 *   DoFTools::make_cell_patches(smoother_data[level].block_list,
1438 *   dof_handler,
1439 *   level);
1440 *  
1441 *   smoother_data[level].relaxation =
1442 *   (settings.smoother_type == "block SOR" ? 1.0 : 0.25);
1443 *   smoother_data[level].inversion = PreconditionBlockBase<double>::svd;
1444 *  
1445 *   std::vector<unsigned int> ordered_indices;
1446 *   switch (settings.dof_renumbering)
1447 *   {
1448 *   case Settings::DoFRenumberingStrategy::downstream:
1449 *   ordered_indices =
1450 *   create_downstream_cell_ordering(dof_handler,
1451 *   advection_direction,
1452 *   level);
1453 *   break;
1454 *  
1455 *   case Settings::DoFRenumberingStrategy::upstream:
1456 *   ordered_indices =
1457 *   create_downstream_cell_ordering(dof_handler,
1458 *   -1.0 * advection_direction,
1459 *   level);
1460 *   break;
1461 *  
1462 *   case Settings::DoFRenumberingStrategy::random:
1463 *   ordered_indices =
1464 *   create_random_cell_ordering(dof_handler, level);
1465 *   break;
1466 *  
1467 *   case Settings::DoFRenumberingStrategy::none:
1468 *   break;
1469 *  
1470 *   default:
1471 *   AssertThrow(false, ExcNotImplemented());
1472 *   break;
1473 *   }
1474 *  
1475 *   smoother_data[level].order =
1476 *   std::vector<std::vector<unsigned int>>(1, ordered_indices);
1477 *   }
1478 *  
1479 *   if (settings.smoother_type == "block SOR")
1480 *   {
1481 *   auto smoother = std::make_unique<MGSmootherPrecondition<
1482 *   SparseMatrix<double>,
1483 *   RelaxationBlockSOR<SparseMatrix<double>, double, Vector<double>>,
1484 *   Vector<double>>>();
1485 *   smoother->initialize(mg_matrices, smoother_data);
1486 *   smoother->set_steps(settings.smoothing_steps);
1487 *   mg_smoother = std::move(smoother);
1488 *   }
1489 *   else if (settings.smoother_type == "block Jacobi")
1490 *   {
1491 *   auto smoother = std::make_unique<
1492 *   MGSmootherPrecondition<SparseMatrix<double>,
1493 *   RelaxationBlockJacobi<SparseMatrix<double>,
1494 *   double,
1495 *   Vector<double>>,
1496 *   Vector<double>>>();
1497 *   smoother->initialize(mg_matrices, smoother_data);
1498 *   smoother->set_steps(settings.smoothing_steps);
1499 *   mg_smoother = std::move(smoother);
1500 *   }
1501 *   }
1502 *   else
1503 *   AssertThrow(false, ExcNotImplemented());
1504 *   }
1505 *  
1506 *  
1507 * @endcode
1508 *
1509 *
1510 * <a name="step_63-codeAdvectionProblemsolvecode"></a>
1511 * <h4><code>AdvectionProblem::solve()</code></h4>
1512 *
1513
1514 *
1515 * Before we can solve the system, we must first set up the multigrid
1516 * preconditioner. This requires the setup of the transfer between levels,
1517 * the coarse matrix solver, and the smoother. This setup follows almost
1518 * identically to @ref step_16 "step-16", the main difference being the various smoothers
1519 * defined above and the fact that we need different interface edge matrices
1520 * for in and out since our problem is non-symmetric. (In reality, for this
1521 * tutorial these interface matrices are empty since we are only using global
1522 * refinement, and thus have no refinement edges. However, we have still
1523 * included both here since if one made the simple switch to an adaptively
1524 * refined method, the program would still run correctly.)
1525 *
1526
1527 *
1528 * The last thing to note is that since our problem is non-symmetric, we must
1529 * use an appropriate Krylov subspace method. We choose here to
1530 * use GMRES since it offers the guarantee of residual reduction in each
1531 * iteration. The major disadvantage of GMRES is that, for each iteration,
1532 * the number of stored temporary vectors increases by one, and one also needs
1533 * to compute a scalar product with all previously stored vectors. This is
1534 * rather expensive. This requirement is relaxed by using the restarted GMRES
1535 * method which puts a cap on the number of vectors we are required to store
1536 * at any one time (here we restart after 50 temporary vectors, or 48
1537 * iterations). This then has the disadvantage that we lose information we
1538 * have gathered throughout the iteration and therefore we could see slower
1539 * convergence. As a consequence, where to restart is a question of balancing
1540 * memory consumption, CPU effort, and convergence speed.
1541 * However, the goal of this tutorial is to have very low
1542 * iteration counts by using a powerful GMG preconditioner, so we have picked
1543 * the restart length such that all of the results shown below converge prior
1544 * to restart happening, and thus we have a standard GMRES method. If the user
1545 * is interested, another suitable method offered in deal.II would be
1546 * BiCGStab.
1547 *
1548
1549 *
1550 *
1551 * @code
1552 *   template <int dim>
1553 *   void AdvectionProblem<dim>::solve()
1554 *   {
1555 *   const unsigned int max_iters = 200;
1556 *   const double solve_tolerance = 1e-8 * system_rhs.l2_norm();
1557 *   SolverControl solver_control(max_iters, solve_tolerance, true, true);
1558 *   solver_control.enable_history_data();
1559 *  
1560 *   using Transfer = MGTransferPrebuilt<Vector<double>>;
1561 *   Transfer mg_transfer(mg_constrained_dofs);
1562 *   mg_transfer.build(dof_handler);
1563 *  
1564 *   FullMatrix<double> coarse_matrix;
1565 *   coarse_matrix.copy_from(mg_matrices[0]);
1566 *   MGCoarseGridHouseholder<double, Vector<double>> coarse_grid_solver;
1567 *   coarse_grid_solver.initialize(coarse_matrix);
1568 *  
1569 *   setup_smoother();
1570 *  
1571 *   mg_matrix.initialize(mg_matrices);
1572 *   mg_interface_matrix_in.initialize(mg_interface_in);
1573 *   mg_interface_matrix_out.initialize(mg_interface_out);
1574 *  
1575 *   Multigrid<Vector<double>> mg(
1576 *   mg_matrix, coarse_grid_solver, mg_transfer, *mg_smoother, *mg_smoother);
1577 *   mg.set_edge_matrices(mg_interface_matrix_out, mg_interface_matrix_in);
1578 *  
1579 *   PreconditionMG<dim, Vector<double>, Transfer> preconditioner(dof_handler,
1580 *   mg,
1581 *   mg_transfer);
1582 *  
1583 *   std::cout << " Solving with GMRES to tol " << solve_tolerance << "..."
1584 *   << std::endl;
1585 *   SolverGMRES<Vector<double>> solver(
1586 *   solver_control, SolverGMRES<Vector<double>>::AdditionalData(50, true));
1587 *  
1588 *   Timer time;
1589 *   time.start();
1590 *   solver.solve(system_matrix, solution, system_rhs, preconditioner);
1591 *   time.stop();
1592 *  
1593 *   std::cout << " converged in " << solver_control.last_step()
1594 *   << " iterations"
1595 *   << " in " << time.last_wall_time() << " seconds " << std::endl;
1596 *  
1597 *   constraints.distribute(solution);
1598 *  
1599 *   mg_smoother.release();
1600 *   }
1601 *  
1602 *  
1603 * @endcode
1604 *
1605 *
1606 * <a name="step_63-codeAdvectionProblemoutput_resultscode"></a>
1607 * <h4><code>AdvectionProblem::output_results()</code></h4>
1608 *
1609
1610 *
1611 * The final function of interest generates graphical output.
1612 * Here we output the solution and cell ordering in a .vtu format.
1613 *
1614
1615 *
1616 * At the top of the function, we generate an index for each cell to
1617 * visualize the ordering used by the smoothers. Note that we do
1618 * this only for the active cells instead of the levels, where the
1619 * smoothers are actually used. For the point smoothers we renumber
1620 * DoFs instead of cells, so this is only an approximation of what
1621 * happens in reality. Finally, the random ordering is not the
1622 * random ordering we actually use (see `create_smoother()` for that).
1623 *
1624
1625 *
1626 * The (integer) ordering of cells is then copied into a (floating
1627 * point) vector for graphical output.
1628 *
1629 * @code
1630 *   template <int dim>
1631 *   void AdvectionProblem<dim>::output_results(const unsigned int cycle) const
1632 *   {
1633 *   const unsigned int n_active_cells = triangulation.n_active_cells();
1634 *   Vector<double> cell_indices(n_active_cells);
1635 *   {
1636 *   std::vector<unsigned int> ordered_indices;
1637 *   switch (settings.dof_renumbering)
1638 *   {
1639 *   case Settings::DoFRenumberingStrategy::downstream:
1640 *   ordered_indices =
1641 *   create_downstream_cell_ordering(dof_handler, advection_direction);
1642 *   break;
1643 *  
1644 *   case Settings::DoFRenumberingStrategy::upstream:
1645 *   ordered_indices =
1646 *   create_downstream_cell_ordering(dof_handler,
1647 *   -1.0 * advection_direction);
1648 *   break;
1649 *  
1650 *   case Settings::DoFRenumberingStrategy::random:
1651 *   ordered_indices = create_random_cell_ordering(dof_handler);
1652 *   break;
1653 *  
1654 *   case Settings::DoFRenumberingStrategy::none:
1655 *   ordered_indices.resize(n_active_cells);
1656 *   for (unsigned int i = 0; i < n_active_cells; ++i)
1657 *   ordered_indices[i] = i;
1658 *   break;
1659 *  
1660 *   default:
1661 *   AssertThrow(false, ExcNotImplemented());
1662 *   break;
1663 *   }
1664 *  
1665 *   for (unsigned int i = 0; i < n_active_cells; ++i)
1666 *   cell_indices(ordered_indices[i]) = static_cast<double>(i);
1667 *   }
1668 *  
1669 * @endcode
1670 *
1671 * The remainder of the function is then straightforward, given
1672 * previous tutorial programs:
1673 *
1674 * @code
1675 *   DataOut<dim> data_out;
1676 *   data_out.attach_dof_handler(dof_handler);
1677 *   data_out.add_data_vector(solution, "solution");
1678 *   data_out.add_data_vector(cell_indices, "cell_index");
1679 *   data_out.build_patches();
1680 *  
1681 *   const std::string filename =
1682 *   "solution-" + Utilities::int_to_string(cycle) + ".vtu";
1683 *   std::ofstream output(filename);
1684 *   data_out.write_vtu(output);
1685 *   }
1686 *  
1687 *  
1688 * @endcode
1689 *
1690 *
1691 * <a name="step_63-codeAdvectionProblemruncode"></a>
1692 * <h4><code>AdvectionProblem::run()</code></h4>
1693 *
1694
1695 *
1696 * As in most tutorials, this function creates/refines the mesh and calls
1697 * the various functions defined above to set up, assemble, solve, and output
1698 * the results.
1699 *
1700
1701 *
1702 * In cycle zero, we generate the mesh for the on the square
1703 * <code>[-1,1]^dim</code> with a hole of radius 3/10 units centered
1704 * at the origin. For objects with `manifold_id` equal to one
1705 * (namely, the faces adjacent to the hole), we assign a spherical
1706 * manifold.
1707 *
1708
1709 *
1710 *
1711 * @code
1712 *   template <int dim>
1713 *   void AdvectionProblem<dim>::run()
1714 *   {
1715 *   for (unsigned int cycle = 0; cycle < (settings.fe_degree == 1 ? 7u : 5u);
1716 *   ++cycle)
1717 *   {
1718 *   std::cout << " Cycle " << cycle << ':' << std::endl;
1719 *  
1720 *   if (cycle == 0)
1721 *   {
1722 *   GridGenerator::hyper_cube_with_cylindrical_hole(triangulation,
1723 *   0.3,
1724 *   1.0);
1725 *  
1726 *   const SphericalManifold<dim> manifold_description(Point<dim>(0, 0));
1727 *   triangulation.set_manifold(1, manifold_description);
1728 *   }
1729 *  
1730 *   triangulation.refine_global();
1731 *  
1732 *   setup_system();
1733 *  
1734 *   std::cout << " Number of active cells: "
1735 *   << triangulation.n_active_cells() << " ("
1736 *   << triangulation.n_levels() << " levels)" << std::endl;
1737 *   std::cout << " Number of degrees of freedom: "
1738 *   << dof_handler.n_dofs() << std::endl;
1739 *  
1740 *   assemble_system_and_multigrid();
1741 *  
1742 *   solve();
1743 *  
1744 *   if (settings.output)
1745 *   output_results(cycle);
1746 *  
1747 *   std::cout << std::endl;
1748 *   }
1749 *   }
1750 *   } // namespace Step63
1751 *  
1752 *  
1753 * @endcode
1754 *
1755 *
1756 * <a name="step_63-Thecodemaincodefunction"></a>
1757 * <h3>The <code>main</code> function</h3>
1758 *
1759
1760 *
1761 * Finally, the main function is like most tutorials. The only
1762 * interesting bit is that we require the user to pass a `.prm` file
1763 * as a sole command line argument. If no parameter file is given, the
1764 * program will output the contents of a sample parameter file with
1765 * all default values to the screen that the user can then copy and
1766 * paste into their own `.prm` file.
1767 *
1768
1769 *
1770 *
1771 * @code
1772 *   int main(int argc, char *argv[])
1773 *   {
1774 *   try
1775 *   {
1776 *   Step63::Settings settings;
1777 *   settings.get_parameters((argc > 1) ? (argv[1]) : "");
1778 *  
1779 *   Step63::AdvectionProblem<2> advection_problem_2d(settings);
1780 *   advection_problem_2d.run();
1781 *   }
1782 *   catch (std::exception &exc)
1783 *   {
1784 *   std::cerr << std::endl
1785 *   << std::endl
1786 *   << "----------------------------------------------------"
1787 *   << std::endl;
1788 *   std::cerr << "Exception on processing: " << std::endl
1789 *   << exc.what() << std::endl
1790 *   << "Aborting!" << std::endl
1791 *   << "----------------------------------------------------"
1792 *   << std::endl;
1793 *   return 1;
1794 *   }
1795 *   catch (...)
1796 *   {
1797 *   std::cerr << std::endl
1798 *   << std::endl
1799 *   << "----------------------------------------------------"
1800 *   << std::endl;
1801 *   std::cerr << "Unknown exception!" << std::endl
1802 *   << "Aborting!" << std::endl
1803 *   << "----------------------------------------------------"
1804 *   << std::endl;
1805 *   return 1;
1806 *   }
1807 *  
1808 *   return 0;
1809 *   }
1810 * @endcode
1811<a name="step_63-Results"></a><h1>Results</h1>
1812
1813
1814<a name="step_63-GMRESIterationNumbers"></a><h3> GMRES Iteration Numbers </h3>
1815
1816
1817The major advantage for GMG is that it is an @f$\mathcal{O}(n)@f$ method,
1818that is, the complexity of the problem increases linearly with the
1819problem size. To show then that the linear solver presented in this
1820tutorial is in fact @f$\mathcal{O}(n)@f$, all one needs to do is show that
1821the iteration counts for the GMRES solve stay roughly constant as we
1822refine the mesh.
1823
1824Each of the following tables gives the GMRES iteration counts to reduce the
1825initial residual by a factor of @f$10^8@f$. We selected a sufficient number of smoothing steps
1826(based on the method) to get iteration numbers independent of mesh size. As
1827can be seen from the tables below, the method is indeed @f$\mathcal{O}(n)@f$.
1828
1829<a name="step_63-DoFCellRenumbering"></a><h4> DoF/Cell Renumbering </h4>
1830
1831
1832The point-wise smoothers ("Jacobi" and "SOR") get applied in the order the
1833DoFs are numbered on each level. We can influence this using the
1834DoFRenumbering namespace. The block smoothers are applied based on the
1835ordering we set in `setup_smoother()`. We can visualize this numbering. The
1836following pictures show the cell numbering of the active cells in downstream,
1837random, and upstream numbering (left to right):
1838
1839<img src="https://www.dealii.org/images/steps/developer/step-63-cell-order.png" alt="">
1840
1841Let us start with the additive smoothers. The following table shows
1842the number of iterations necessary to obtain convergence from GMRES:
1843
1844<table align="center" class="doxtable">
1845<tr>
1846 <th></th>
1847 <th></th>
1848 <th colspan="1">@f$Q_1@f$</th>
1849 <th colspan="7">Smoother (smoothing steps)</th>
1850</tr>
1851<tr>
1852 <th></th>
1853 <th></th>
1854 <th></th>
1855 <th colspan="3">Jacobi (6)</th>
1856 <th></th>
1857 <th colspan="3">Block Jacobi (3)</th>
1858</tr>
1859<tr>
1860 <th></th>
1861 <th></th>
1862 <th></th>
1863 <th colspan="3">Renumbering Strategy</th>
1864 <th></th>
1865 <th colspan="3">Renumbering Strategy</th>
1866</tr>
1867<tr>
1868 <th>Cells</th>
1869 <th></th>
1870 <th>DoFs</th>
1871 <th>Downstream</th>
1872 <th>Random</th>
1873 <th>Upstream</th>
1874 <th></th>
1875 <th>Downstream</th>
1876 <th>Random</th>
1877 <th>Upstream</th>
1878</tr>
1879<tr>
1880 <th>32</th>
1881 <th></th>
1882 <th>48</th>
1883 <td>3</th>
1884 <td>3</th>
1885 <td>3</th>
1886 <th></th>
1887 <td>3</th>
1888 <td>3</th>
1889 <td>3</th>
1890</tr>
1891<tr>
1892 <th>128</th>
1893 <th></th>
1894 <th>160</th>
1895 <td>6</th>
1896 <td>6</th>
1897 <td>6</th>
1898 <th></th>
1899 <td>6</th>
1900 <td>6</th>
1901 <td>6</th>
1902</tr>
1903<tr>
1904 <th>512</th>
1905 <th></th>
1906 <th>576</th>
1907 <td>11</th>
1908 <td>11</th>
1909 <td>11</th>
1910 <th></th>
1911 <td>9</th>
1912 <td>9</th>
1913 <td>9</th>
1914</tr>
1915<tr>
1916 <th>2048</th>
1917 <th></th>
1918 <th>2176</th>
1919 <td>15</th>
1920 <td>15</th>
1921 <td>15</th>
1922 <th></th>
1923 <td>13</th>
1924 <td>13</th>
1925 <td>13</th>
1926</tr>
1927<tr>
1928 <th>8192</th>
1929 <th></th>
1930 <th>8448</th>
1931 <td>18</th>
1932 <td>18</th>
1933 <td>18</th>
1934 <th></th>
1935 <td>15</th>
1936 <td>15</th>
1937 <td>15</th>
1938</tr>
1939<tr>
1940 <th>32768</th>
1941 <th></th>
1942 <th>33280</th>
1943 <td>20</th>
1944 <td>20</th>
1945 <td>20</th>
1946 <th></th>
1947 <td>16</th>
1948 <td>16</th>
1949 <td>16</th>
1950</tr>
1951<tr>
1952 <th>131072</th>
1953 <th></th>
1954 <th>132096</th>
1955 <td>20</th>
1956 <td>20</th>
1957 <td>20</th>
1958 <th></th>
1959 <td>16</th>
1960 <td>16</th>
1961 <td>16</th>
1962</tr>
1963</table>
1964
1965We see that renumbering the
1966DoFs/cells has no effect on convergence speed. This is because these
1967smoothers compute operations on each DoF (point-smoother) or cell
1968(block-smoother) independently and add up the results. Since we can
1969define these smoothers as an application of a sum of matrices, and
1970matrix addition is commutative, the order at which we sum the
1971different components will not affect the end result.
1972
1973On the other hand, the situation is different for multiplicative smoothers:
1974
1975<table align="center" class="doxtable">
1976<tr>
1977 <th></th>
1978 <th></th>
1979 <th colspan="1">@f$Q_1@f$</th>
1980 <th colspan="7">Smoother (smoothing steps)</th>
1981</tr>
1982<tr>
1983 <th></th>
1984 <th></th>
1985 <th></th>
1986 <th colspan="3">SOR (3)</th>
1987 <th></th>
1988 <th colspan="3">Block SOR (1)</th>
1989</tr>
1990<tr>
1991 <th></th>
1992 <th></th>
1993 <th></th>
1994 <th colspan="3">Renumbering Strategy</th>
1995 <th></th>
1996 <th colspan="3">Renumbering Strategy</th>
1997</tr>
1998<tr>
1999 <th>Cells</th>
2000 <th></th>
2001 <th>DoFs</th>
2002 <th>Downstream</th>
2003 <th>Random</th>
2004 <th>Upstream</th>
2005 <th></th>
2006 <th>Downstream</th>
2007 <th>Random</th>
2008 <th>Upstream</th>
2009</tr>
2010<tr>
2011 <th>32</th>
2012 <th></th>
2013 <th>48</th>
2014 <td>2</th>
2015 <td>2</th>
2016 <td>3</th>
2017 <th></th>
2018 <td>2</th>
2019 <td>2</th>
2020 <td>3</th>
2021</tr>
2022<tr>
2023 <th>128</th>
2024 <th></th>
2025 <th>160</th>
2026 <td>5</th>
2027 <td>5</th>
2028 <td>7</th>
2029 <th></th>
2030 <td>5</th>
2031 <td>5</th>
2032 <td>7</th>
2033</tr>
2034<tr>
2035 <th>512</th>
2036 <th></th>
2037 <th>576</th>
2038 <td>7</th>
2039 <td>9</th>
2040 <td>11</th>
2041 <th></th>
2042 <td>7</th>
2043 <td>7</th>
2044 <td>12</th>
2045</tr>
2046<tr>
2047 <th>2048</th>
2048 <th></th>
2049 <th>2176</th>
2050 <td>10</th>
2051 <td>12</th>
2052 <td>15</th>
2053 <th></th>
2054 <td>8</th>
2055 <td>10</th>
2056 <td>17</th>
2057</tr>
2058<tr>
2059 <th>8192</th>
2060 <th></th>
2061 <th>8448</th>
2062 <td>11</th>
2063 <td>15</th>
2064 <td>19</th>
2065 <th></th>
2066 <td>10</th>
2067 <td>11</th>
2068 <td>20</th>
2069</tr>
2070<tr>
2071 <th>32768</th>
2072 <th></th>
2073 <th>33280</th>
2074 <td>12</th>
2075 <td>16</th>
2076 <td>20</th>
2077 <th></th>
2078 <td>10</th>
2079 <td>12</th>
2080 <td>21</th>
2081</tr>
2082<tr>
2083 <th>131072</th>
2084 <th></th>
2085 <th>132096</th>
2086 <td>12</th>
2087 <td>16</th>
2088 <td>19</th>
2089 <th></th>
2090 <td>11</th>
2091 <td>12</th>
2092 <td>21</th>
2093</tr>
2094</table>
2095
2096Here, we can speed up
2097convergence by renumbering the DoFs/cells in the advection direction,
2098and similarly, we can slow down convergence if we do the renumbering
2099in the opposite direction. This is because advection-dominated
2100problems have a directional flow of information (in the advection
2101direction) which, given the right renumbering of DoFs/cells,
2102multiplicative methods are able to capture.
2103
2104This feature of multiplicative methods is, however, dependent on the
2105value of @f$\varepsilon@f$. As we increase @f$\varepsilon@f$ and the problem
2106becomes more diffusion-dominated, we have a more uniform propagation
2107of information over the mesh and there is a diminished advantage for
2108renumbering in the advection direction. On the opposite end, in the
2109extreme case of @f$\varepsilon=0@f$ (advection-only), we have a 1st-order
2110PDE and multiplicative methods with the right renumbering become
2111effective solvers: A correct downstream numbering may lead to methods
2112that require only a single iteration because information can be
2113propagated from the inflow boundary downstream, with no information
2114transport in the opposite direction. (Note, however, that in the case
2115of @f$\varepsilon=0@f$, special care must be taken for the boundary
2116conditions in this case).
2117
2118
2119<a name="step_63-Pointvsblocksmoothers"></a><h4> %Point vs. block smoothers </h4>
2120
2121
2122We will limit the results to runs using the downstream
2123renumbering. Here is a cross comparison of all four smoothers for both
2124@f$Q_1@f$ and @f$Q_3@f$ elements:
2125
2126<table align="center" class="doxtable">
2127<tr>
2128 <th></th>
2129 <td></th>
2130 <th colspan="1">@f$Q_1@f$</th>
2131 <th colspan="4">Smoother (smoothing steps)</th>
2132 <th></th>
2133 <th colspan="1">@f$Q_3@f$</th>
2134 <th colspan="4">Smoother (smoothing steps)</th>
2135</tr>
2136<tr>
2137 <th colspan="1">Cells</th>
2138 <td></th>
2139 <th colspan="1">DoFs</th>
2140 <th colspan="1">Jacobi (6)</th>
2141 <th colspan="1">Block Jacobi (3)</th>
2142 <th colspan="1">SOR (3)</th>
2143 <th colspan="1">Block SOR (1)</th>
2144 <th></th>
2145 <th colspan="1">DoFs</th>
2146 <th colspan="1">Jacobi (6)</th>
2147 <th colspan="1">Block Jacobi (3)</th>
2148 <th colspan="1">SOR (3)</th>
2149 <th colspan="1">Block SOR (1)</th>
2150</tr>
2151<tr>
2152 <th>32</th>
2153 <td></th>
2154 <th>48</th>
2155 <td>3</th>
2156 <td>3</th>
2157 <td>2</th>
2158 <td>2</th>
2159 <td></th>
2160 <th>336</th>
2161 <td>15</th>
2162 <td>14</th>
2163 <td>15</th>
2164 <td>6</th>
2165</tr>
2166<tr>
2167 <th>128</th>
2168 <td></th>
2169 <th>160</th>
2170 <td>6</th>
2171 <td>6</th>
2172 <td>5</th>
2173 <td>5</th>
2174 <td></th>
2175 <th>1248</th>
2176 <td>23</th>
2177 <td>18</th>
2178 <td>21</th>
2179 <td>9</th>
2180</tr>
2181<tr>
2182 <th>512</th>
2183 <td></th>
2184 <th>576</th>
2185 <td>11</th>
2186 <td>9</th>
2187 <td>7</th>
2188 <td>7</th>
2189 <td></th>
2190 <th>4800</th>
2191 <td>29</th>
2192 <td>21</th>
2193 <td>28</th>
2194 <td>9</th>
2195</tr>
2196<tr>
2197 <th>2048</th>
2198 <td></th>
2199 <th>2176</th>
2200 <td>15</th>
2201 <td>13</th>
2202 <td>10</th>
2203 <td>8</th>
2204 <td></th>
2205 <th>18816</th>
2206 <td>33</th>
2207 <td>22</th>
2208 <td>32</th>
2209 <td>9</th>
2210</tr>
2211<tr>
2212 <th>8192</th>
2213 <td></th>
2214 <th>8448</th>
2215 <td>18</th>
2216 <td>15</th>
2217 <td>11</th>
2218 <td>10</th>
2219 <td></th>
2220 <th>74496</th>
2221 <td>35</th>
2222 <td>22</th>
2223 <td>34</th>
2224 <td>10</th>
2225</tr>
2226<tr>
2227 <th>32768</th>
2228 <td></th>
2229 <th>33280</th>
2230 <td>20</th>
2231 <td>16</th>
2232 <td>12</th>
2233 <td>10</th>
2234 <td></th>
2235</tr>
2236<tr>
2237 <th>131072</th>
2238 <td></th>
2239 <th>132096</th>
2240 <td>20</th>
2241 <td>16</th>
2242 <td>12</th>
2243 <td>11</th>
2244 <td></th>
2245</tr>
2246</table>
2247
2248We see that for @f$Q_1@f$, both multiplicative smoothers require a smaller
2249combination of smoothing steps and iteration counts than either
2250additive smoother. However, when we increase the degree to a @f$Q_3@f$
2251element, there is a clear advantage for the block smoothers in terms
2252of the number of smoothing steps and iterations required to
2253solve. Specifically, the block SOR smoother gives constant iteration
2254counts over the degree, and the block Jacobi smoother only sees about
2255a 38% increase in iterations compared to 75% and 183% for Jacobi and
2256SOR respectively.
2257
2258<a name="step_63-Cost"></a><h3> Cost </h3>
2259
2260
2261Iteration counts do not tell the full story in the optimality of a one
2262smoother over another. Obviously we must examine the cost of an
2263iteration. Block smoothers here are at a disadvantage as they are
2264having to construct and invert a cell matrix for each cell. Here is a
2265comparison of solve times for a @f$Q_3@f$ element with 74,496 DoFs:
2266
2267<table align="center" class="doxtable">
2268<tr>
2269 <th colspan="1">@f$Q_3@f$</th>
2270 <th colspan="4">Smoother (smoothing steps)</th>
2271</tr>
2272<tr>
2273 <th colspan="1">DoFs</th>
2274 <th colspan="1">Jacobi (6)</th>
2275 <th colspan="1">Block Jacobi (3)</th>
2276 <th colspan="1">SOR (3)</th>
2277 <th colspan="1">Block SOR (1)</th>
2278</tr>
2279<tr>
2280 <th>74496</th>
2281 <td>0.68s</th>
2282 <td>5.82s</th>
2283 <td>1.18s</th>
2284 <td>1.02s</th>
2285</tr>
2286</table>
2287
2288The smoother that requires the most iterations (Jacobi) actually takes
2289the shortest time (roughly 2/3 the time of the next fastest
2290method). This is because all that is required to apply a Jacobi
2291smoothing step is multiplication by a diagonal matrix which is very
2292cheap. On the other hand, while SOR requires over 3x more iterations
2293(each with 3x more smoothing steps) than block SOR, the times are
2294roughly equivalent, implying that a smoothing step of block SOR is
2295roughly 9x slower than a smoothing step of SOR. Lastly, block Jacobi
2296is almost 6x more expensive than block SOR, which intuitively makes
2297sense from the fact that 1 step of each method has the same cost
2298(inverting the cell matrices and either adding or multiply them
2299together), and block Jacobi has 3 times the number of smoothing steps per
2300iteration with 2 times the iterations.
2301
2302
2303<a name="step_63-Additionalpoints"></a><h3> Additional points </h3>
2304
2305
2306There are a few more important points to mention:
2307
2308<ol>
2309<li> For a mesh distributed in parallel, multiplicative methods cannot
2310be executed over the entire domain. This is because they operate one
2311cell at a time, and downstream cells can only be handled once upstream
2312cells have already been done. This is fine on a single processor: The
2313processor just goes through the list of cells one after the
2314other. However, in parallel, it would imply that some processors are
2315idle because upstream processors have not finished doing the work on
2316cells upstream from the ones owned by the current processor. Once the
2317upstream processors are done, the downstream ones can start, but by
2318that time the upstream processors have no work left. In other words,
2319most of the time during these smoother steps, most processors are in
2320fact idle. This is not how one obtains good parallel scalability!
2321
2322One can use a hybrid method where
2323a multiplicative smoother is applied on each subdomain, but as you
2324increase the number of subdomains, the method approaches the behavior
2325of an additive method. This is a major disadvantage to these methods.
2326</li>
2327
2328<li> Current research into block smoothers suggest that soon we will be
2329able to compute the inverse of the cell matrices much cheaper than
2330what is currently being done inside deal.II. This research is based on
2331the fast diagonalization method (dating back to the 1960s) and has
2332been used in the spectral community for around 20 years (see, e.g., <a
2333href="https://doi.org/10.1007/s10915-004-4787-3"> Hybrid
2334Multigrid/Schwarz Algorithms for the Spectral Element Method by Lottes
2335and Fischer</a>). There are currently efforts to generalize these
2336methods to DG and make them more robust. Also, it seems that one
2337should be able to take advantage of matrix-free implementations and
2338the fact that, in the interior of the domain, cell matrices tend to
2339look very similar, allowing fewer matrix inverse computations.
2340</li>
2341</ol>
2342
2343Combining 1. and 2. gives a good reason for expecting that a method
2344like block Jacobi could become very powerful in the future, even
2345though currently for these examples it is quite slow.
2346
2347
2348<a name="step_63-Possibilitiesforextensions"></a><h3> Possibilities for extensions </h3>
2349
2350
2351<a name="step_63-ConstantiterationsforQsub5sub"></a><h4> Constant iterations for Q<sub>5</sub> </h4>
2352
2353
2354Change the number of smoothing steps and the smoother relaxation
2355parameter (set in <code>Smoother::AdditionalData()</code> inside
2356<code>create_smoother()</code>, only necessary for point smoothers) so
2357that we maintain a constant number of iterations for a @f$Q_5@f$ element.
2358
2359<a name="step_63-Effectivenessofrenumberingforchangingepsilon"></a><h4> Effectiveness of renumbering for changing epsilon </h4>
2360
2361
2362Increase/decrease the parameter "Epsilon" in the `.prm` files of the
2363multiplicative methods and observe for which values renumbering no
2364longer influences convergence speed.
2365
2366<a name="step_63-Meshadaptivity"></a><h4> Mesh adaptivity </h4>
2367
2368
2369The code is set up to work correctly with an adaptively refined mesh (the
2370interface matrices are created and set). Devise a suitable refinement
2371criterium or try the KellyErrorEstimator class.
2372 *
2373 *
2374<a name="step_63-PlainProg"></a>
2375<h1> The plain program</h1>
2376@include "step-63.cc"
2377*/
Definition fe_q.h:554
virtual void value_list(const std::vector< Point< dim > > &points, std::vector< RangeNumberType > &values, const unsigned int component=0) const
virtual RangeNumberType value(const Point< dim > &p, const unsigned int component=0) const
void declare_entry(const std::string &entry, const std::string &default_value, const Patterns::PatternBase &pattern=Patterns::Anything(), const std::string &documentation="", const bool has_to_be_set=false)
Definition point.h:111
DerivativeForm< 1, spacedim, dim, Number > transpose(const DerivativeForm< 1, dim, spacedim, Number > &DF)
Point< 2 > first
Definition grid_out.cc:4623
unsigned int level
Definition grid_out.cc:4626
#define Assert(cond, exc)
#define AssertDimension(dim1, dim2)
#define AssertThrow(cond, exc)
void mesh_loop(const CellIteratorType &begin, const CellIteratorType &end, const CellWorkerFunctionType &cell_worker, const CopierType &copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const AssembleFlags flags=assemble_own_cells, const BoundaryWorkerFunctionType &boundary_worker=BoundaryWorkerFunctionType(), const FaceWorkerFunctionType &face_worker=FaceWorkerFunctionType(), const unsigned int queue_length=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
Definition mesh_loop.h:281
void make_hanging_node_constraints(const DoFHandler< dim, spacedim > &dof_handler, AffineConstraints< number > &constraints)
void make_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern, const AffineConstraints< number > &constraints={}, const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
#define DEAL_II_NOT_IMPLEMENTED()
std::vector< index_type > data
Definition mpi.cc:735
std::size_t size
Definition mpi.cc:734
Expression coth(const Expression &x)
void downstream(DoFHandler< dim, spacedim > &dof_handler, const Tensor< 1, spacedim > &direction, const bool dof_wise_renumbering=false)
void random(DoFHandler< dim, spacedim > &dof_handler)
IndexSet extract_locally_relevant_level_dofs(const DoFHandler< dim, spacedim > &dof_handler, const unsigned int level)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
@ matrix
Contents is actually a matrix.
@ symmetric
Matrix is symmetric.
void make_interface_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, const MGConstrainedDoFs &mg_constrained_dofs, SparsityPatternBase &sparsity, const unsigned int level)
Definition mg_tools.cc:1013
void make_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity, const unsigned int level, const AffineConstraints< number > &constraints={}, const bool keep_constrained_dofs=true)
Definition mg_tools.cc:575
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
Definition utilities.cc:191
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
SymmetricTensor< 2, dim, Number > epsilon(const Tensor< 2, dim, Number > &Grad_u)
void interpolate_boundary_values(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const std::map< types::boundary_id, const Function< spacedim, number > * > &function_map, std::map< types::global_dof_index, number > &boundary_values, const ComponentMask &component_mask={})
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
int(&) functions(const void *v1, const void *v2)
static constexpr double PI
Definition numbers.h:254
STL namespace.
::VectorizedArray< Number, width > exp(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
DEAL_II_HOST constexpr Number trace(const SymmetricTensor< 2, dim2, Number > &)