deal.II version 9.7.0
\(\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-93.h
Go to the documentation of this file.
1) const
618 *   {
619 *   if ((center - p).norm() <= radius)
620 *   return 1;
621 *   else
622 *   return 0;
623 *   }
624 *  
625 *  
626 * @endcode
627 *
628 *
629 * <a name="step_93-Theprincipalclass"></a>
630 * <h3>The principal class</h3>
631 *
632
633 *
634 * The main class is very similar to @ref step_4 "step-4" in structure, given that this is
635 * a relatively simple program that does not use adaptive mesh refinement.
636 * However, there are three new member variables:
637 *
638
639 *
640 * - `nonlocal_dofs`: A `std::vector` of dof indices that stores the
641 * dof index for the nonlocal dofs.
642 *
643
644 *
645 * - `heat_functions`: A `std::vector` of CircularIndicatorFunction objects;
646 * these are the heat sources.
647 *
648
649 *
650 * - `target_function`: This is the function we want to match. We store it
651 * as a class variable because it is used both in assemble_system() and
652 * output_results().
653 *
654 * @code
655 *   template <int dim>
656 *   class Step93
657 *   {
658 *   public:
659 *   Step93();
660 *  
661 *   void run();
662 *  
663 *   private:
664 *   void make_grid();
665 *   void setup_system();
666 *   void assemble_system();
667 *   void solve();
668 *   void output_results() const;
669 *  
670 *   Triangulation<dim> triangulation;
671 *   DoFHandler<dim> dof_handler;
672 *  
673 *   hp::FECollection<dim> fe_collection;
674 *   hp::QCollection<dim> quadrature_collection;
675 *  
676 *   AffineConstraints<double> constraints;
677 *  
678 *   SparsityPattern sparsity_pattern;
679 *   SparseMatrix<double> system_matrix;
680 *  
681 *   Vector<double> solution;
682 *   Vector<double> system_rhs;
683 *  
684 *   std::vector<types::global_dof_index> nonlocal_dofs;
685 *  
686 *   std::vector<CircularIndicatorFunction<dim>> heat_functions;
687 *  
688 *   const TargetFunction<dim> target_function;
689 *   };
690 *  
691 *  
692 * @endcode
693 *
694 *
695 * <a name="step_93-TheStep93constructor"></a>
696 * <h4>The Step93 constructor</h4>
697 * In this constructor, we set up several of the fundamental data
698 * structures this program needs. Specifically, we generate the
699 * finite element collection, which is basically a list of all the
700 * possible finite elements we could use on each cell. This
701 * collection has two elements: one FESystem that has two degree 2
702 * FE_Q elements and one FE_Nothing element (to be used on all cells
703 * that are not "special"), and one FESystem that has two degree 2
704 * FE_Q elements and one degree 0 FE_DGQ element (to be used on
705 * those "special" cells we use to anchor the non-local degrees of
706 * freedom, as discussed in the introduction).
707 *
708
709 *
710 * Where we have a collection of elements, we then also need a
711 * collection of quadratures -- which here has only a single element
712 * because we can use the same quadrature on all cells.
713 *
714
715 *
716 * The default constructor also initializes
717 * dof_handler and target_function,
718 * and it generates a vector of CircularIndicatorFunctions objects which
719 * represent the heat functions.
720 *
721 * @code
722 *   template <int dim>
723 *   Step93<dim>::Step93()
724 *   : dof_handler(triangulation)
725 *   , target_function(3,
726 *   (dim == 1 ? Point<dim>(0.5) :
727 *   dim == 2 ? Point<dim>(0.5, 0.5) :
728 *   Point<dim>(0.5, 0.5, 0.5)))
729 *   {
730 * @endcode
731 *
732 * Here, we generate the finite element collection, which is basically a
733 * list of all the possible finite elements we could use on each cell. This
734 * collection has two elements: one FESystem that has two degree 2 FE_Q
735 * elements and one FE_Nothing element, and one FESystem that has two
736 * degree 2 FE_Q elements and one degree 0 FE_DGQ element.
737 *
738 * @code
739 *   fe_collection.push_back(
740 *   FESystem<dim>(FE_Q<dim>(2), 2, FE_Nothing<dim>(), 1));
741 *   fe_collection.push_back(FESystem<dim>(FE_Q<dim>(2), 2, FE_DGQ<dim>(0), 1));
742 *  
743 * @endcode
744 *
745 * The quadrature collection is just one degree 3 QGauss element.
746 *
747 * @code
748 *   quadrature_collection.push_back(QGauss<dim>(3));
749 *  
750 * @endcode
751 *
752 * Here, we choose the center points for the heat functions to be the
753 * vertices of a lattice, in this case the corners of a hypercube
754 * centered at 0, with side length 1. The block of code below
755 * then creates the CircularIndicatorFunction
756 * objects for the main class. To make these, we check the dimension of the
757 * problem and create 2, 4, or 8 function objects.
758 *
759 * @code
760 *   switch (dim)
761 *   {
762 *   case (1):
763 *   heat_functions.emplace_back(Point<dim>({0.5}), 0.2);
764 *   heat_functions.emplace_back(Point<dim>({-0.5}), 0.2);
765 *   break;
766 *   case (2):
767 *   heat_functions.emplace_back(Point<dim>({0.5, 0.5}), 0.2);
768 *   heat_functions.emplace_back(Point<dim>({0.5, -0.5}), 0.2);
769 *   heat_functions.emplace_back(Point<dim>({-0.5, 0.5}), 0.2);
770 *   heat_functions.emplace_back(Point<dim>({-0.5, -0.5}), 0.2);
771 *   break;
772 *   case (3):
773 *   heat_functions.emplace_back(Point<dim>({0.5, 0.5, 0.5}), 0.2);
774 *   heat_functions.emplace_back(Point<dim>({0.5, 0.5, -0.5}), 0.2);
775 *   heat_functions.emplace_back(Point<dim>({0.5, -0.5, 0.5}), 0.2);
776 *   heat_functions.emplace_back(Point<dim>({0.5, -0.5, -0.5}), 0.2);
777 *   heat_functions.emplace_back(Point<dim>({-0.5, 0.5, 0.5}), 0.2);
778 *   heat_functions.emplace_back(Point<dim>({-0.5, 0.5, -0.5}), 0.2);
779 *   heat_functions.emplace_back(Point<dim>({-0.5, -0.5, 0.5}), 0.2);
780 *   heat_functions.emplace_back(Point<dim>({-0.5, -0.5, -0.5}), 0.2);
781 *   break;
782 *   default:
784 *   break;
785 *   }
786 *   }
787 *  
788 * @endcode
789 *
790 *
791 * <a name="step_93-Step93make_grid"></a>
792 * <h4>Step93::make_grid()</h4>
793 *
794
795 *
796 * The make_grid() function makes a hypercube grid, see @ref step_4 "step-4":
797 *
798 * @code
799 *   template <int dim>
800 *   void Step93<dim>::make_grid()
801 *   {
802 *   GridGenerator::hyper_cube(triangulation, -1, 1);
803 *   triangulation.refine_global(7);
804 *  
805 *   std::cout << "Number of active cells: " << triangulation.n_active_cells()
806 *   << std::endl;
807 *   }
808 *  
809 *  
810 * @endcode
811 *
812 *
813 * <a name="step_93-Step93setup_system"></a>
814 * <h4>Step93::setup_system()</h4>
815 *
816
817 *
818 * The `setup_system()` function is similar to @ref step_4 "step-4", except we have to add a
819 * few steps to prepare for the nonlocal dofs.
820 *
821 * @code
822 *   template <int dim>
823 *   void Step93<dim>::setup_system()
824 *   {
825 * @endcode
826 *
827 * Here, we loop over the cells and set the FESystem index to 1, which
828 * corresponds to the system with 2 FE_Q elements and one FE_DGQ element. We
829 * do this until we have enough dofs for each heat function. Note that we
830 * use the global active cell index to measure when to stop the
831 * loop. This allows the loop to be run in parallel with no alteration.
832 * Then, we call DoFHandler::distribute_dofs() to actually enumerate all
833 * degrees of freedom.
834 *
835 * @code
836 *   for (const auto &cell : dof_handler.active_cell_iterators())
837 *   {
838 *   if (cell->global_active_cell_index() < heat_functions.size())
839 *   {
840 *   cell->set_active_fe_index(1);
841 *   }
842 *   else
843 *   {
844 *   break;
845 *   }
846 *   }
847 *   dof_handler.distribute_dofs(fe_collection);
848 *  
849 * @endcode
850 *
851 * Once we've assigned dofs, the code block below counts the
852 * number of dofs in the system, and outputs to the console. In
853 * other contexts, we might want to use *block* matrices (see, for
854 * example, @ref step_20 "step-20" or @ref step_22 "step-22") to build more efficient linear
855 * solvers; here, we will just put everything into one big matrix
856 * and so knowing the number of unknowns for each of the variables
857 * is purely for informational purposes.
858 *
859 * @code
860 *   const std::vector<types::global_dof_index> dofs_per_component =
861 *   DoFTools::count_dofs_per_fe_component(dof_handler);
862 *   const unsigned int n_dofs_u = dofs_per_component[0],
863 *   n_dofs_l = dofs_per_component[1],
864 *   n_dofs_c = dofs_per_component[2];
865 *   std::cout << "Number of degrees of freedom: " << n_dofs_u << "+" << n_dofs_l
866 *   << "+" << n_dofs_c << " = " << n_dofs_u + n_dofs_l + n_dofs_c
867 *   << std::endl;
868 *  
869 * @endcode
870 *
871 * Finally, we need to extract the indices of the finite elements which
872 * correspond to the non-local dofs.
873 *
874
875 *
876 * First, we make a component mask, which is `false` except for
877 * the third component. This will extract only the dofs from the
878 * third component of the FE system. Next, we actually extract the
879 * dofs, and store them in an IndexSet variable. Finally, we add
880 * each extracted index to the member array `nonlocal_dofs`.
881 *
882 * @code
883 *   const ComponentMask component_mask_c({false, false, true});
884 *   const IndexSet indices_c =
885 *   DoFTools::extract_dofs(dof_handler, component_mask_c);
886 *  
887 *   for (const types::global_dof_index non_local_index : indices_c)
888 *   nonlocal_dofs.push_back(non_local_index);
889 *  
890 *   std::cout << "Number of nonlocal dofs: " << nonlocal_dofs.size()
891 *   << std::endl;
892 *  
893 *  
894 * @endcode
895 *
896 * The mesh we created above is not locally refined, and so there
897 * are no hanging node constraints to keep track of. But it does
898 * not hurt to just use the same setup we have used starting in
899 * @ref step_6 "step-6" of building a constraints object that contains hanging
900 * node constraints (anticipating that perhaps we'd want to do
901 * adaptive mesh refinement in a later step) into which we then
902 * also put the constraints for boundary values on both the @f$u@f$
903 * and @f$\lambda@f$ variables.
904 *
905
906 *
907 * Because the nonlocal degrees of freedom use discontinuous
908 * elements, they do not contribute to boundary values
909 * (discontinuous elements do not have degrees of freedom
910 * logically located on the boundary that could be interpolated)
911 * and we do not need to exclude these solution components
912 * explicitly when calling
914 *
915 * @code
916 *   constraints.clear();
917 *   DoFTools::make_hanging_node_constraints(dof_handler, constraints);
919 *   0,
921 *   constraints);
922 *   constraints.close();
923 *  
924 * @endcode
925 *
926 * The remainder of the function deals with building the sparsity
927 * pattern. It consists of two parts: The entries that result from
928 * the usual integration of products of shape functions, and then
929 * the entries that result from integrals that contain nonlocal
930 * degrees of freedom (which one can think of as associated with
931 * shape functions that are constant across the entire
932 * domain). The first part is easily built using standard tools:
933 *
934 * @code
935 *   DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
936 *   DoFTools::make_sparsity_pattern(dof_handler, dsp, constraints, false);
937 *  
938 * @endcode
939 *
940 * The other part is more awkward. We have matrix entries that
941 * result from terms such as @f$\int_{\Omega}\varphi_j f_k@f$ where
942 * each @f$\varphi_j@f$ is a shape function associated to @f$\lambda@f$
943 * and each @f$f_k@f$ is a
944 * characteristic function of a part of the domain. As a
945 * consequence, we end up with a nonzero matrix entry @f$A_{jk}@f$
946 * (along with its transpose @f$A_{kj}@f$) if there is overlap between
947 * @f$\varphi_j@f$ and @f$f_k@f$. In practice, because we will use
948 * quadrature, this means that we end up with a quadrature point
949 * on a cell on which @f$\varphi_j@f$ lives and at which @f$f_k@f$ is not
950 * zero. (We will implicitly assume that a shape function that
951 * lives on the current cell is nonzero at all quadrature points
952 * -- an assumption that is generally true unless one chooses
953 * specific quadrature formulas.) Determining which sparsity
954 * pattern entries we need to add then essentially comes down to
955 * "simulating" what would happen if we actually computed
956 * integrals and which matrix entries would end up being non-zero
957 * in the process. As a consequence, the following code's general
958 * structure looks very similar to what we will do for the
959 * nonlocal contributions in the `assemble_system()` function
960 * below.
961 *
962
963 *
964 * To get this started, we create an `hp_fe_values` that we
965 * will only use to query the quadrature point locations. The
966 * non-local dofs will need to interact with the second component
967 * of the fe system (namely, @f$\lambda@f$), so we also declare a
968 * variable that will help us extract this scalar field for use
969 * below.
970 *
971 * @code
972 *   hp::FEValues<dim> hp_fe_values(fe_collection,
973 *   quadrature_collection,
974 *   update_quadrature_points);
975 *  
976 * @endcode
977 *
978 * Then, we loop over the cells, then over the quadrature points, and
979 * finally over the indices, as if we were constructing a mass matrix.
980 * However, what we instead do here is check two things. First, we check if
981 * the quadrature point is within the radius of a circular indicator
982 * function that represents our non-local dof. If so
983 * then we add an entry to the sparse matrix at the
984 * (nonlocal dof index, lambda dof index) entry and the (lambda dof index,
985 * nonlocal dof index) entry for all lambda degrees of freedom. (Because the
986 * matrix we solve with has both the lambda-nonlocal interacting block and
987 * its transpose, we need to add two entries each time.)
988 *
989 * @code
990 *   std::vector<types::global_dof_index> local_dof_indices;
991 *   for (const auto &cell : dof_handler.active_cell_iterators())
992 *   {
993 *   hp_fe_values.reinit(cell);
994 *  
995 *   const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values();
996 *  
997 *   local_dof_indices.resize(fe_values.dofs_per_cell);
998 *   cell->get_dof_indices(local_dof_indices);
999 *  
1000 *   for (const unsigned int q_index : fe_values.quadrature_point_indices())
1001 *   {
1002 *   const Point<dim> q_point = fe_values.quadrature_point(q_index);
1003 *   for (const unsigned int i : fe_values.dof_indices())
1004 *   {
1005 *   if (fe_values.get_fe().system_to_component_index(i).first ==
1006 *   1) // 'i' is a lambda shape function
1007 *   {
1008 *   for (unsigned int j = 0; j < heat_functions.size(); ++j)
1009 *   if (heat_functions[j].value(q_point) != 0)
1010 *   {
1011 *   dsp.add(local_dof_indices[i], nonlocal_dofs[j]);
1012 *   dsp.add(nonlocal_dofs[j], local_dof_indices[i]);
1013 *   }
1014 *   }
1015 *   }
1016 *   }
1017 *   }
1018 *  
1019 * @endcode
1020 *
1021 * The rest (below) is standard setup code, see @ref step_4 "step-4":
1022 *
1023 * @code
1024 *   sparsity_pattern.copy_from(dsp);
1025 *  
1026 *   system_matrix.reinit(sparsity_pattern);
1027 *  
1028 *   solution.reinit(dof_handler.n_dofs());
1029 *   system_rhs.reinit(dof_handler.n_dofs());
1030 *   }
1031 *  
1032 *  
1033 * @endcode
1034 *
1035 *
1036 * <a name="step_93-Step93assemble_system"></a>
1037 * <h4>Step93::assemble_system()</h4>
1038 *
1039
1040 *
1041 * The `assemble_system()` function works very similar to how is
1042 * does in other tutorial programs (cf. @ref step_4 "step-4", @ref step_6 "step-6", @ref step_8 "step-8", and
1043 * for the vector-valued case see @ref step_22 "step-22"). However, there is an
1044 * additional component to constructing the system matrix, because
1045 * we need to handle the nonlocal dofs manually.
1046 *
1047 * @code
1048 *   template <int dim>
1049 *   void Step93<dim>::assemble_system()
1050 *   {
1051 * @endcode
1052 *
1053 * First, we do a standard loop setup for constructing the system matrix.
1054 *
1055 * @code
1056 *   hp::FEValues<dim> hp_fe_values(fe_collection,
1057 *   quadrature_collection,
1058 *   update_values | update_gradients |
1059 *   update_quadrature_points |
1060 *   update_JxW_values);
1061 *  
1062 *   FullMatrix<double> cell_matrix;
1063 *   Vector<double> cell_rhs;
1064 *  
1065 *   std::vector<types::global_dof_index> local_dof_indices;
1066 *  
1067 *   const FEValuesExtractors::Scalar u(0);
1068 *   const FEValuesExtractors::Scalar lambda(1);
1069 *  
1070 *   for (const auto &cell : dof_handler.active_cell_iterators())
1071 *   {
1072 *   const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();
1073 *  
1074 *   cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
1075 *   cell_rhs.reinit(dofs_per_cell);
1076 *   hp_fe_values.reinit(cell);
1077 *  
1078 *   cell_matrix = 0;
1079 *   cell_rhs = 0;
1080 *  
1081 *   const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values();
1082 *  
1083 *   local_dof_indices.resize(fe_values.dofs_per_cell);
1084 *  
1085 *   cell->get_dof_indices(local_dof_indices);
1086 *  
1087 *  
1088 * @endcode
1089 *
1090 * In the loop over quadrature points, we start by building
1091 * all of the usual terms that are bilinear in shape functions
1092 * corresponding to the @f$u@f$ and @f$\lambda@f$ variables:
1093 *
1094 * @code
1095 *   for (const unsigned int q_index : fe_values.quadrature_point_indices())
1096 *   {
1097 *   const double JxW = fe_values.JxW(q_index);
1098 *   for (const unsigned int i : fe_values.dof_indices())
1099 *   {
1100 *   const double phi_i_u = fe_values[u].value(i, q_index),
1101 *   phi_i_l = fe_values[lambda].value(i, q_index);
1102 *  
1103 *   const Tensor<1, dim> grad_i_u =
1104 *   fe_values[u].gradient(i, q_index),
1105 *   grad_i_l =
1106 *   fe_values[lambda].gradient(i, q_index);
1107 *  
1108 *   for (const unsigned int j : fe_values.dof_indices())
1109 *   {
1110 *   const double phi_j_u = fe_values[u].value(j, q_index);
1111 *  
1112 *   const Tensor<1, dim> grad_j_u =
1113 *   fe_values[u].gradient(j, q_index),
1114 *   grad_j_l =
1115 *   fe_values[lambda].gradient(j,
1116 *   q_index);
1117 *  
1118 *   cell_matrix(i, j) += phi_i_u * phi_j_u * JxW;
1119 *   cell_matrix(i, j) += -grad_i_u * grad_j_l * JxW;
1120 *   cell_matrix(i, j) += -grad_i_l * grad_j_u * JxW;
1121 *   }
1122 *  
1123 *   const Point<dim> q_point = fe_values.quadrature_point(q_index);
1124 *   cell_rhs(i) += (phi_i_u * target_function.value(q_point) * JxW);
1125 *  
1126 *  
1127 * @endcode
1128 *
1129 * For the integrals that involve the nonlocal dofs,
1130 * we make use of the quadrature point again. To
1131 * compute the integrals, we loop over the
1132 * heat functions, adding the numeric integral of each
1133 * heat equation with each @f$\lambda@f$ shape function,
1134 * at the appropriate indices (which we found in
1135 * `setup_system()`). Note that if we try to add 0 to
1136 * a matrix entry we have not previously indicated should
1137 * be nonzero, there will not be a problem; but if we
1138 * try to add a nonzero value to an entry not
1139 * previously added to the sparsity pattern, we will
1140 * get an error. In other words, the following lines
1141 * of the code check that we adjusted the sparsity
1142 * pattern correctly in the previous function.
1143 *
1144 * @code
1145 *   for (unsigned int j = 0; j < heat_functions.size(); ++j)
1146 *   {
1147 *   system_matrix.add(local_dof_indices[i],
1148 *   nonlocal_dofs[j],
1149 *   heat_functions[j].value(q_point) *
1150 *   phi_i_l * JxW);
1151 *   system_matrix.add(nonlocal_dofs[j],
1152 *   local_dof_indices[i],
1153 *   heat_functions[j].value(q_point) *
1154 *   phi_i_l * JxW);
1155 *   }
1156 *   }
1157 *   }
1158 *  
1159 * @endcode
1160 *
1161 * Finally, we copy the local contributions to the linear
1162 * system into the global matrix and right hand side vector,
1163 * taking into account hanging node and boundary values
1164 * constraints:
1165 *
1166 * @code
1167 *   constraints.distribute_local_to_global(
1168 *   cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
1169 *   }
1170 *   }
1171 *  
1172 *  
1173 * @endcode
1174 *
1175 *
1176 * <a name="step_93-Step93solve"></a>
1177 * <h4>Step93::solve()</h4>
1178 *
1179
1180 *
1181 * The solve() function works similar to how it is done in @ref step_6 "step-6"
1182 * and @ref step_8 "step-8", except we need to use a different solver because the
1183 * linear problem we are trying to solve is a saddle point problem
1184 * for which the Conjugate Gradient algorithm is not
1185 * applicable. But, because the matrix is symmetric, we can use
1186 * SolverMinRes, an iterative solver specialized for symmetric
1187 * indefinite problems. This solver could be improved with the use
1188 * of preconditioners, but we don't do that here for simplicity (see
1189 * the Possibilities for Extensions section below).
1190 *
1191
1192 *
1193 * As you will see in the output, given that we are not using a
1194 * preconditioner, we need a *lot* of iterations to solve this
1195 * linear system. We set the maximum to one million, more than we
1196 * need of course, but an indication that this is not an efficient
1197 * solver. For smaller problems, one can also use a direct solver
1198 * (see @ref step_29 "step-29") for which you would just replace the main part of
1199 * this function by the following three lines of code:
1200 * <div class=CodeFragmentInTutorialComment>
1201 * @code
1202 * SparseDirectUMFPACK direct_solver;
1203 * direct_solver.initialize(system_matrix);
1204 * direct_solver.vmult(solution, system_rhs);
1205 * @endcode
1206 * </div>
1207 *
1208 * @code
1209 *   template <int dim>
1210 *   void Step93<dim>::solve()
1211 *   {
1212 *   Timer timer;
1213 *   timer.start();
1214 *  
1215 *   std::cout << "Beginning solve..." << std::endl;
1216 *  
1217 *   SolverControl solver_control(1'000'000, 1e-6 * system_rhs.l2_norm());
1218 *   SolverMinRes<Vector<double>> solver(solver_control);
1219 *  
1220 *   solver.solve(system_matrix, solution, system_rhs, PreconditionIdentity());
1221 *  
1222 *   timer.stop();
1223 *  
1224 *   std::cout << "Wall time: " << timer.wall_time() << "s" << std::endl;
1225 *   std::cout << "Solved in " << solver_control.last_step()
1226 *   << " MINRES iterations." << std::endl;
1227 *   }
1228 *  
1229 *  
1230 * @endcode
1231 *
1232 *
1233 * <a name="step_93-Step93output_results"></a>
1234 * <h4>Step93::output_results()</h4>
1235 *
1236
1237 *
1238 * The `output_results()` function is a bit more robust for this program than
1239 * is typical. This is because, in order to visualize the heat sources we have
1240 * optimized, we need to do extra work and interpolate them onto a mesh. We do
1241 * this by instantiating a new DoFHandler object and then using the helper
1242 * function VectorTools::interpolate().
1243 *
1244
1245 *
1246 * The top of the function is as always when using vector-valued
1247 * elements (see, for example, @ref step_22 "step-22") and simply outputs all of
1248 * the solution variables on the mesh cells they are defined on:
1249 *
1250 * @code
1251 *   template <int dim>
1252 *   void Step93<dim>::output_results() const
1253 *   {
1254 *   const std::vector<std::string> solution_names = {"u", "lambda", "c"};
1255 *  
1256 *   const std::vector<DataComponentInterpretation::DataComponentInterpretation>
1260 *  
1261 *   DataOut<dim> data_out;
1262 *   data_out.add_data_vector(dof_handler,
1263 *   solution,
1264 *   solution_names,
1265 *   interpretation);
1266 *  
1267 * @endcode
1268 *
1269 * The non-local degrees of freedom are of course defined on the
1270 * first several cells, but at least logically are considered to
1271 * live everywhere (or, if you prefer, nowhere at all since they
1272 * do not represent spatial functions). But, conceptually, we use
1273 * them as multipliers for the heat sources, and so while the
1274 * coefficients @f$C^k@f$ are non-local, the heat source @f$\sum_k C^k
1275 * f_k(\mathbf x)@f$ *is* a spatially variable function. It would be
1276 * nice if we could visualize that as well. The same is true for
1277 * the target heat distribution @f$\bar u@f$ we are trying to match.
1278 *
1279
1280 *
1281 * To do so, we
1282 * create a new dof handler to output the target function
1283 * and heat plate values, and associate it with
1284 * a finite element with a degree that matches what we used to solve for @f$u@f$
1285 * and @f$\lambda@f$, although in reality this is an arbitrary choice:
1286 *
1287 * @code
1288 *   DoFHandler<dim> new_dof_handler(triangulation);
1289 *  
1290 *   const FE_Q<dim> new_fe(2);
1291 *   new_dof_handler.distribute_dofs(new_fe);
1292 *  
1293 * @endcode
1294 *
1295 * To get started with the visualization, we need a vector which
1296 * stores the interpolated target function. We create the vector,
1297 * interpolate the target function @f$\bar u@f$ onto the mesh, then
1298 * add the data to our data_out object.
1299 *
1300 * @code
1301 *   Vector<double> target(new_dof_handler.n_dofs());
1302 *   VectorTools::interpolate(new_dof_handler,
1304 *   [&](const Point<dim> &x) {
1305 *   return target_function.value(x, 0);
1306 *   }),
1307 *   target);
1308 *   data_out.add_data_vector(new_dof_handler, target, "u_bar");
1309 *  
1310 * @endcode
1311 *
1312 * In order to visualize the sum of the heat sources @f$\sum_k C^k
1313 * f_k(\mathbf x)@f$, we create a vector which will store the
1314 * interpolated values of this function. Then, we loop through
1315 * the heat functions, create a vector to store the interpolated
1316 * data, call the VectorTools::interpolate() function to fill the
1317 * vector, multiply the interpolated data by the nonlocal dof
1318 * value @f$C^k@f$ (so that the heat plate is set to the correct
1319 * temperature), and then add this data to the sum of heat
1320 * sources. Because we can, we also add the vector for each source
1321 * individually to the DataOut object, so that they can be
1322 * visualized individually.
1323 *
1324 * @code
1325 *   Vector<double> full_heat_profile(new_dof_handler.n_dofs());
1326 *  
1327 *   for (unsigned int i = 0; i < heat_functions.size(); ++i)
1328 *   {
1329 *   Vector<double> hot_plate_i(new_dof_handler.n_dofs());
1330 *  
1331 *   VectorTools::interpolate(new_dof_handler,
1332 *   heat_functions[i],
1333 *   hot_plate_i);
1334 *  
1335 *   hot_plate_i *= solution[nonlocal_dofs[i]];
1336 *   full_heat_profile += hot_plate_i;
1337 *  
1338 *   const std::string data_name =
1339 *   "Heat_Source_" + Utilities::int_to_string(i);
1340 *   data_out.add_data_vector(new_dof_handler, hot_plate_i, data_name);
1341 *   }
1342 *  
1343 * @endcode
1344 *
1345 * Once all the heat functions have been combined, we add them to the
1346 * data_out object, and output everything into a file :
1347 *
1348 * @code
1349 *   data_out.add_data_vector(new_dof_handler,
1350 *   full_heat_profile,
1351 *   "Full_Heat_Profile");
1352 *  
1353 *   data_out.build_patches();
1354 *  
1355 *   std::ofstream output("solution.vtu");
1356 *   data_out.write_vtu(output);
1357 *  
1358 * @endcode
1359 *
1360 * Finally, we output the nonlocal coefficient values to the console:
1361 *
1362 * @code
1363 *   std::cout << "The c coefficients are " << std::endl;
1364 *   for (long unsigned int i = 0; i < nonlocal_dofs.size(); ++i)
1365 *   {
1366 *   std::cout << "\tc" << i + 1 << ": " << solution[nonlocal_dofs[i]]
1367 *   << std::endl;
1368 *   }
1369 *   }
1370 *  
1371 *  
1372 * @endcode
1373 *
1374 *
1375 * <a name="step_93-Step93run"></a>
1376 * <h4>Step93::run()</h4>
1377 *
1378
1379 *
1380 * The run() function runs through each step of the program, nothing new here:
1381 *
1382 * @code
1383 *   template <int dim>
1384 *   void Step93<dim>::run()
1385 *   {
1386 *   make_grid();
1387 *   setup_system();
1388 *   assemble_system();
1389 *   solve();
1390 *   output_results();
1391 *   }
1392 *   } // namespace Step93
1393 *  
1394 *  
1395 * @endcode
1396 *
1397 *
1398 * <a name="step_93-Themainfunction"></a>
1399 * <h3>The main() function</h3>
1400 *
1401
1402 *
1403 * The `main()` function looks essentially like that of most other tutorial
1404 * programs.
1405 *
1406 * @code
1407 *   int main()
1408 *   {
1409 *   try
1410 *   {
1411 *   Step93::Step93<2> heat_optimization_problem;
1412 *   heat_optimization_problem.run();
1413 *   }
1414 *   catch (std::exception &exc)
1415 *   {
1416 *   std::cerr << std::endl
1417 *   << std::endl
1418 *   << "----------------------------------------------------"
1419 *   << std::endl;
1420 *   std::cerr << "Exception on processing: " << std::endl
1421 *   << exc.what() << std::endl
1422 *   << "Aborting!" << std::endl
1423 *   << "----------------------------------------------------"
1424 *   << std::endl;
1425 *  
1426 *   return 1;
1427 *   }
1428 *   catch (...)
1429 *   {
1430 *   std::cerr << std::endl
1431 *   << std::endl
1432 *   << "----------------------------------------------------"
1433 *   << std::endl;
1434 *   std::cerr << "Unknown exception!" << std::endl
1435 *   << "Aborting!" << std::endl
1436 *   << "----------------------------------------------------"
1437 *   << std::endl;
1438 *   return 1;
1439 *   }
1440 *  
1441 *   return 0;
1442 *   }
1443 * @endcode
1444<a name="step_93-Results"></a><h1>Results</h1>
1445
1446
1447When you run the program with the step target function (in 2D), the output looks something like this:
1448
1449@code
1450Number of active cells: 16384
1451Number of degrees of freedom: 66049+66049+4 = 132102
1452Number of nonlocal dofs: 4
1453Beginning solve...
1454Wall time: 63.9265s
1455Solved in 39973 MINRES iterations.
1456The c coefficients are
1457 c1: 28.7408
1458 c2: -6.51604
1459 c3: -6.51604
1460 c4: -1.62044
1461@endcode
1462
1463When you run the program with the Gaussian target function (in 2D), the output should look like this:
1464
1465@code
1466Number of active cells: 16384
1467Number of degrees of freedom: 66049+66049+4 = 132102
1468Number of nonlocal dofs: 4
1469Beginning solve...
1470Wall time: 98.4858s
1471Solved in 62131 MINRES iterations.
1472The c coefficients are
1473 c1: 23.553
1474 c2: -4.86562
1475 c3: -4.86562
1476 c4: -1.42344
1477@endcode
1478
1479The goal of this program is to determine which temperature settings best match the target function, so first
1480let's see what these targets look like:
1481
1482<table align="center" class="doxtable">
1483 <tr>
1484 <td>
1485 <center><b>Step Target %Function</b></center>
1486 </td>
1487 <td>
1488 <center><b>Gaussian Target %Function</b></center>
1489 </td>
1490 </tr>
1491 <tr>
1492 <td>
1493 <img src="https://www.dealii.org/images/steps/developer/step-93.target_step.png"
1494 alt="Step target function"
1495 width="90%">
1496 </td>
1497 <td>
1498 <img src="https://www.dealii.org/images/steps/developer/step-93.target_gauss.png"
1499 alt="Gaussian target function"
1500 width="90%">
1501 </td>
1502 </tr>
1503</table>
1504
1505After solving the Lagrangian system, we arrive at solutions @f$U_\text{step}@f$ and @f$U_\text{gauss}@f$ that
1506look like this:
1507
1508<table align="center" class="doxtable">
1509 <tr>
1510 <td>
1511 <center><b>@f$U_\text{step}@f$</b></center>
1512 </td>
1513 <td>
1514 <center><b>@f$U_\text{gauss}@f$</b></center>
1515 </td>
1516 </tr>
1517 <tr>
1518 <td>
1519 <img src="https://www.dealii.org/images/steps/developer/step-93.U_step.png"
1520 alt="Solution for step shaped target function"
1521 width="90%">
1522 </td>
1523 <td>
1524 <img src="https://www.dealii.org/images/steps/developer/step-93.U_gauss.png"
1525 alt="Solution for Gaussian target function"
1526 width="90%">
1527 </td>
1528 </tr>
1529</table>
1530
1531Notice that @f$U_\text{gauss}@f$ matches the target much better than
1532@f$U_\text{step}@f$. Intuitively, this makes sense: in general, solutions
1533to the heat equation look something like Gaussians, so the
1534Gaussian target function is a much more "natural" thing to match than
1535a sharp step function. We can also see this in the optimal heat
1536profiles.
1537
1538<table align="center" class="doxtable">
1539 <tr>
1540 <td>
1541 <center><b>Heat plate settings for matching step function</b></center>
1542 </td>
1543 <td>
1544 <center><b>Heat plate settings for matching Gaussian</b></center>
1545 </td>
1546 </tr>
1547 <tr>
1548 <td>
1549 <img src="https://www.dealii.org/images/steps/developer/step-93.heat_profile_step.png"
1550 alt="Heat plate settings for matching step function"
1551 width="90%">
1552 </td>
1553 <td>
1554 <img src="https://www.dealii.org/images/steps/developer/step-93.heat_profile_gauss.png"
1555 alt="Heat plate settings for matching Gaussian"
1556 width="90%">
1557 </td>
1558 </tr>
1559</table>
1560
1561Notice that for the Gaussian target, the 4 plates are set to less extreme values. In contrast, to try to match the step function, higher and lower temperatures must be applied.
1562
1563While it does not contain much useful information, we can also plot the Lagrange multiplier @f$\Lambda@f$, which has an interesting shape:
1564
1565<table align="center" class="doxtable">
1566 <tr>
1567 <td>
1568 <center><b>@f$\Lambda_\text{step}@f$</b></center>
1569 </td>
1570 <td>
1571 <center><b>@f$\Lambda_\text{gauss}@f$</b></center>
1572 </td>
1573 </tr>
1574 <tr>
1575 <td>
1576 <img src="https://www.dealii.org/images/steps/developer/step-93.L_step.png"
1577 alt="Lagrange multiplier for step target function"
1578 width="90%">
1579 </td>
1580 <td>
1581 <img src="https://www.dealii.org/images/steps/developer/step-93.L_gauss.png"
1582 alt="Lagrange multiplier for Gaussian target function"
1583 width="90%">
1584 </td>
1585 </tr>
1586</table>
1587
1588
1589
1590<a name="step_93-Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
1591
1592
1593There are a few ways that this program could be extended, which we list below.
1594
15951. As mentioned in the code documentation, this program does not make
1596use of any preconditioners before solving. This is because, for a 2D
1597problem, the code runs fast enough that optimization is not
1598necessary. However, as shown in the screen output above, the number of
1599iterations required to solve the linear system is quite large. Thus,
1600for larger problems, it would be good if the solver ran more
1601quickly. See the "Possibilities for extensions" section of @ref step_6 "step-6" for
1602a more detailed discussion on how to change preconditioners. We should
1603note that since the block matrix we use has many zeros on the
1604diagonal, preconditioners like PreconditionJacobi will not work
1605because they divide by diagonal entries. Instead, block
1606preconditioners such as those discussed in @ref step_20 "step-20" or @ref step_22 "step-22" (among
1607many others) will likely be useful. For block preconditioners, the
1608key realizations is that the blocks of the system matrix
1609@f{align*}{
1610 \left(\begin{array}{c c c}
1611 \mathcal{M} & -\mathcal{N}^T & 0\\
1612 -\mathcal{N} & 0 & \mathcal{F}^T\\
1613 0 & \mathcal{F} & 0
1614 \end{array}\right)
1615@f}
1616can often individually be solved with quite efficiently; for example,
1617@f$\mathcal{M}@f$ is a mass matrix that is easily solved with using a CG
1618iteration, and @f$\mathcal N@f$ is a Laplace matrix for which CG with
1619a geometric or algebraic multigrid preconditioner is very effective.
1620Using the ideas of @ref step_20 "step-20" and @ref step_22 "step-22", we should then create a
1621@f$3\times 3@f$ block preconditioner in which some blocks correspond to the
1622inverses of @f$\mathcal{M}@f$ or @f$\mathcal{N}@f$, or some kind of Schur
1623complement. A starting point for this kind of consideration is
1624@cite Battermann_1998 .
1625
16262. To validate the optimization problem is working correctly, we could
1627try to match a target function which is itself a solution to the
1628Poisson equation with prescribed heat profile. If the optimization
1629problem is being solved correctly, it should be able to perfectly
1630match this solution. To create such a function, we would need to first
1631solve the Poisson problem on a scalar field, with a RHS described by
1632the chosen heat profile. See @ref step_7 "step-7" for more information on the method
1633of manufactured solutions.
1634
16353. The program at the moment has the number of nonlocal degrees of freedom
1636hardcoded as @f$2^d@f$ (see the constructor). We then assign each of these
1637degrees of freedom to one of the first @f$2^d@f$ cells. This is not going to
1638be much of a problem because there are always enough cells for this as
1639long as you start with a mesh that is at least once refined. What would
1640we do if we had a number of nonlocal DoFs that is not easily predictable,
1641and that may be larger than the number of cells? Perhaps a better approach
1642would be to come up with a way to assign *all* of these to the first cell,
1643because there is *always* at least one cell. The way to achieve this would
1644be to replace the use of FE_DGQ(0) (an element with exactly one degree of
1645freedom) by an element that has more than one -- and in particular exactly
1646the right number of degrees of freedom -- and that can be used on the first
1647cell; all other cells would then use FE_Nothing. At the time of writing
1648this program, there is no element class that can easily be given a *specific*
1649number of degrees of freedom, but it would not be very difficult to write
1650such a class.
1651 *
1652 *
1653<a name="step_93-PlainProg"></a>
1654<h1> The plain program</h1>
1655@include "step-93.cc"
1656*/
void add_data_vector(const VectorType &data, const std::vector< std::string > &names, const DataVectorType type=type_automatic, const std::vector< DataComponentInterpretation::DataComponentInterpretation > &data_component_interpretation={})
void distribute_dofs(const FiniteElement< dim, spacedim > &fe)
Definition fe_q.h:554
Definition point.h:113
void initialize(const SparsityPattern &sparsity_pattern)
Definition timer.h:117
void start()
Definition timer.cc:176
DerivativeForm< 1, spacedim, dim, Number > transpose(const DerivativeForm< 1, dim, spacedim, Number > &DF)
const unsigned int DoFAccessor< structdim, dim, spacedim, level_dof_access >::dimension
#define DEAL_II_ASSERT_UNREACHABLE()
void loop(IteratorType begin, std_cxx20::type_identity_t< IteratorType > end, DOFINFO &dinfo, INFOBOX &info, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &cell_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &boundary_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &, typename INFOBOX::CellInfo &)> &face_worker, AssemblerType &assembler, const LoopControl &lctrl=LoopControl())
Definition loop.h:564
const bool IsBlockVector< VectorType >::value
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)
void interpolate(const DoFHandler< dim, spacedim > &dof1, const InVector &u1, const DoFHandler< dim, spacedim > &dof2, OutVector &u2)
void hyper_cube(Triangulation< dim, spacedim > &tria, const double left=0., const double right=1., const bool colorize=false)
@ matrix
Contents is actually a matrix.
constexpr types::blas_int zero
constexpr char A
constexpr types::blas_int one
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim > > > &Du)
Definition divergence.h:471
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
VectorType::value_type * end(VectorType &V)
T sum(const T &t, const MPI_Comm mpi_communicator)
std::string int_to_string(const unsigned int value, const unsigned int digits=numbers::invalid_unsigned_int)
Definition utilities.cc:466
void interpolate(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Function< spacedim, typename VectorType::value_type > &function, VectorType &vec, const ComponentMask &component_mask={}, const unsigned int level=numbers::invalid_unsigned_int)
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)
bool check(const ConstraintKinds kind_in, const unsigned int dim)
int(& functions)(const void *v1, const void *v2)