deal.II version GIT relicensing-7040-g409d9ea789 2026-11-06 04:40:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
step-3.h
Go to the documentation of this file.
1 1)
770 *   , dof_handler(triangulation)
771 *   {}
772 *  
773 *  
774 * @endcode
775 *
776 *
777 * <a name="step_3-Step3make_grid"></a>
778 * <h4>Step3::make_grid</h4>
779 *
780
781 *
782 * Now, the first thing we've got to do is to generate the triangulation on
783 * which we would like to do our computation and number each vertex with a
784 * degree of freedom. We have seen these two steps in @ref step_1 "step-1" and @ref step_2 "step-2"
785 * before, respectively.
786 *
787
788 *
789 * This function does the first part, creating the mesh. We create the grid
790 * and refine all cells five times. Since the initial grid (which is the
791 * square @f$[-1,1] \times [-1,1]@f$) consists of only one cell, the final grid
792 * has 32 times 32 cells, for a total of 1024.
793 *
794
795 *
796 * Unsure that 1024 is the correct number? We can check that by outputting the
797 * number of cells using the <code>n_active_cells()</code> function on the
798 * triangulation.
799 *
800 * @code
801 *   void Step3::make_grid()
802 *   {
803 *   GridGenerator::hyper_cube(triangulation, -1, 1);
804 *   triangulation.refine_global(5);
805 *  
806 *   std::cout << "Number of active cells: " << triangulation.n_active_cells()
807 *   << std::endl;
808 *   }
809 *  
810 * @endcode
811 *
812 * @note We call the Triangulation::n_active_cells() function, rather than
813 * Triangulation::n_cells(). Here, <i>active</i> means the cells that aren't
814 * refined any further. We stress the adjective "active" since there are more
815 * cells, namely the parent cells of the finest cells, their parents, etc, up
816 * to the one cell which made up the initial grid. Of course, on the next
817 * coarser level, the number of cells is one quarter that of the cells on the
818 * finest level, i.e. 256, then 64, 16, 4, and 1. If you called
819 * <code>triangulation.n_cells()</code> instead in the code above, you would
820 * consequently get a value of 1365 instead. On the other hand, the number of
821 * cells (as opposed to the number of active cells) is not typically of much
822 * interest, so there is no good reason to print it.
823 *
824
825 *
826 *
827
828 *
829 *
830 * <a name="step_3-Step3setup_system"></a>
831 * <h4>Step3::setup_system</h4>
832 *
833
834 *
835 * Next we enumerate all the degrees of freedom and set up matrix and vector
836 * objects to hold the system data. Enumerating is done by using
837 * DoFHandler::distribute_dofs(), as we have seen in the @ref step_2 "step-2" example. Since
838 * we use the FE_Q class and have set the polynomial degree to 1 in the
839 * constructor, i.e. bilinear elements, this associates one degree of freedom
840 * with each vertex. While we're at generating output, let us also take a look
841 * at how many degrees of freedom are generated:
842 *
843 * @code
844 *   void Step3::setup_system()
845 *   {
846 *   dof_handler.distribute_dofs(fe);
847 *   std::cout << "Number of degrees of freedom: " << dof_handler.n_dofs()
848 *   << std::endl;
849 * @endcode
850 *
851 * There should be one DoF for each vertex. Since we have a 32 times 32
852 * grid, the number of DoFs should be 33 times 33, or 1089.
853 *
854
855 *
856 * As we have seen in the previous example, we set up a sparsity pattern by
857 * first creating a temporary structure, tagging those entries that might be
858 * nonzero, and then copying the data over to the SparsityPattern object
859 * that can then be used by the system matrix.
860 *
861 * @code
862 *   DynamicSparsityPattern dsp(dof_handler.n_dofs());
863 *   DoFTools::make_sparsity_pattern(dof_handler, dsp);
864 *   sparsity_pattern.copy_from(dsp);
865 *  
866 * @endcode
867 *
868 * Note that the SparsityPattern object does not hold the values of the
869 * matrix, it only stores the places where entries are. The entries
870 * themselves are stored in objects of type SparseMatrix, of which our
871 * variable system_matrix is one.
872 *
873
874 *
875 * The distinction between sparsity pattern and matrix was made to allow
876 * several matrices to use the same sparsity pattern. This may not seem
877 * relevant here, but when you consider the size which matrices can have,
878 * and that it may take some time to build the sparsity pattern, this
879 * becomes important in large-scale problems if you have to store several
880 * matrices in your program.
881 *
882 * @code
883 *   system_matrix.reinit(sparsity_pattern);
884 *  
885 * @endcode
886 *
887 * The last thing to do in this function is to set the sizes of the right
888 * hand side vector and the solution vector to the right values:
889 *
890 * @code
891 *   solution.reinit(dof_handler.n_dofs());
892 *   system_rhs.reinit(dof_handler.n_dofs());
893 *   }
894 *  
895 * @endcode
896 *
897 *
898 * <a name="step_3-Step3assemble_system"></a>
899 * <h4>Step3::assemble_system</h4>
900 *
901
902 *
903 *
904
905 *
906 * The next step is to compute the entries of the matrix and right hand side
907 * that form the linear system from which we compute the solution. This is the
908 * central function of each finite element program and we have discussed the
909 * primary steps in the introduction already.
910 *
911
912 *
913 * The general approach to assemble matrices and vectors is to loop over all
914 * cells, and on each cell compute the contribution of that cell to the global
915 * matrix and right hand side by quadrature. The point to realize now is that
916 * we need the values of the shape functions at the locations of quadrature
917 * points on the real cell. However, both the finite element shape functions
918 * as well as the quadrature points are only defined on the reference
919 * cell. They are therefore of little help to us, and we will in fact hardly
920 * ever query information about finite element shape functions or quadrature
921 * points from these objects directly.
922 *
923
924 *
925 * Rather, what is required is a way to map this data from the reference cell
926 * to the real cell. Classes that can do that are derived from the Mapping
927 * class, though one again often does not have to deal with them directly:
928 * many functions in the library can take a mapping object as argument, but
929 * when it is omitted they simply resort to the standard bilinear Q1
930 * mapping. We will go this route, and not bother with it for the moment (we
931 * come back to this in @ref step_10 "step-10", @ref step_11 "step-11", and @ref step_12 "step-12").
932 *
933
934 *
935 * So what we now have is a collection of three classes to deal with: finite
936 * element, quadrature, and mapping objects. That's too much, so there is one
937 * type of class that orchestrates information exchange between these three:
938 * the FEValues class. If given one instance of each three of these objects
939 * (or two, and an implicit linear mapping), it will be able to provide you
940 * with information about values and gradients of shape functions at
941 * quadrature points on a real cell.
942 *
943
944 *
945 * Using all this, we will assemble the linear system for this problem in the
946 * following function:
947 *
948 * @code
949 *   void Step3::assemble_system()
950 *   {
951 * @endcode
952 *
953 * Ok, let's start: we need a quadrature formula for the evaluation of the
954 * integrals on each cell. Let's take a Gauss formula with two quadrature
955 * points in each direction, i.e. a total of four points since we are in
956 * 2d. This quadrature formula integrates polynomials of degrees up to three
957 * exactly (in 1d). It is easy to check that this is sufficient for the
958 * present problem:
959 *
960 * @code
961 *   const QGauss<2> quadrature_formula(fe.degree + 1);
962 * @endcode
963 *
964 * And we initialize the object which we have briefly talked about above. It
965 * needs to be told which finite element we want to use, and the quadrature
966 * points and their weights (jointly described by a Quadrature object). As
967 * mentioned, we use the implied Q1 mapping, rather than specifying one
968 * ourselves explicitly. Finally, we have to tell it what we want it to
969 * compute on each cell: we need the values of the shape functions at the
970 * quadrature points (for the right hand side @f$(\varphi_i,f)@f$), their
971 * gradients (for the matrix entries @f$(\nabla \varphi_i, \nabla
972 * \varphi_j)@f$), and also the weights of the quadrature points and the
973 * determinants of the Jacobian transformations from the reference cell to
974 * the real cells.
975 *
976
977 *
978 * This list of what kind of information we actually need is given as a
979 * collection of flags as the third argument to the constructor of
980 * FEValues. Since these values have to be recomputed, or updated, every
981 * time we go to a new cell, all of these flags start with the prefix
982 * <code>update_</code> and then indicate what it actually is that we want
983 * updated. The flag to give if we want the values of the shape functions
984 * computed is #update_values; for the gradients it is
985 * #update_gradients. The determinants of the Jacobians and the quadrature
986 * weights are always used together, so only the products (Jacobians times
987 * weights, or short <code>JxW</code>) are computed; since we need them, we
988 * have to list #update_JxW_values as well:
989 *
990 * @code
991 *   FEValues<2> fe_values(fe,
992 *   quadrature_formula,
994 * @endcode
995 *
996 * The advantage of this approach is that we can specify what kind of
997 * information we actually need on each cell. It is easily understandable
998 * that this approach can significantly speed up finite element computations,
999 * compared to approaches where everything, including second derivatives,
1000 * normal vectors to cells, etc are computed on each cell, regardless of
1001 * whether they are needed or not.
1002 *
1003
1004 *
1005 * @note The syntax <code>update_values | update_gradients |
1006 * update_JxW_values</code> is not immediately obvious to anyone not
1007 * used to programming bit operations in C for years already. First,
1008 * <code>operator|</code> is the <i>bitwise or operator</i>, i.e.,
1009 * it takes two integer arguments that are interpreted as bit
1010 * patterns and returns an integer in which every bit is set for
1011 * which the corresponding bit is set in at least one of the two
1012 * arguments. For example, consider the operation
1013 * <code>9|10</code>. In binary, <code>9=0b1001</code> (where the
1014 * prefix <code>0b</code> indicates that the number is to be
1015 * interpreted as a binary number) and <code>10=0b1010</code>. Going
1016 * through each bit and seeing whether it is set in one of the
1017 * argument, we arrive at <code>0b1001|0b1010=0b1011</code> or, in
1018 * decimal notation, <code>9|10=11</code>. The second piece of
1019 * information you need to know is that the various
1020 * <code>update_*</code> flags are all integers that have <i>exactly
1021 * one bit set</i>. For example, assume that
1022 * <code>update_values=0b00001=1</code>,
1023 * <code>update_gradients=0b00010=2</code>,
1024 * <code>update_JxW_values=0b10000=16</code>. Then
1026 * 0b10011 = 19</code>. In other words, we obtain a number that
1027 * <i>encodes a binary mask representing all of the operations you
1028 * want to happen</i>, where each operation corresponds to exactly
1029 * one bit in the integer that, if equal to one, means that a
1030 * particular piece should be updated on each cell and, if it is
1031 * zero, means that we need not compute it. In other words, even
1032 * though <code>operator|</code> is the <i>bitwise OR operation</i>,
1033 * what it really represents is <i>I want this AND that AND the
1034 * other</i>. Such binary masks are quite common in C programming,
1035 * but maybe not so in higher level languages like C++, but serve
1036 * the current purpose quite well.
1037 *
1038
1039 *
1040 * For use further down below, we define a shortcut for a value that will
1041 * be used very frequently. Namely, an abbreviation for the number of degrees
1042 * of freedom on each cell (since we are in 2d and degrees of freedom are
1043 * associated with vertices only, this number is four, but we rather want to
1044 * write the definition of this variable in a way that does not preclude us
1045 * from later choosing a different finite element that has a different
1046 * number of degrees of freedom per cell, or work in a different space
1047 * dimension).
1048 *
1049
1050 *
1051 * In general, it is a good idea to use a symbolic name instead of
1052 * hard-coding these numbers even if you know them, since for example,
1053 * you may want to change the finite element at some time. Changing the
1054 * element would have to be done in a different function and it is easy
1055 * to forget to make a corresponding change in another part of the program.
1056 * It is better to not rely on your own calculations, but instead ask
1057 * the right object for the information: Here, we ask the finite element
1058 * to tell us about the number of degrees of freedom per cell and we
1059 * will get the correct number regardless of the space dimension or
1060 * polynomial degree we may have chosen elsewhere in the program.
1061 *
1062
1063 *
1064 * The shortcut here, defined primarily to discuss the basic concept
1065 * and not because it saves a lot of typing, will then make the following
1066 * loops a bit more readable. You will see such shortcuts in many places in
1067 * larger programs, and `dofs_per_cell` is one that is more or less the
1068 * conventional name for this kind of object.
1069 *
1070 * @code
1071 *   const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
1072 *  
1073 * @endcode
1074 *
1075 * Now, we said that we wanted to assemble the global matrix and vector
1076 * cell-by-cell. We could write the results directly into the global matrix,
1077 * but this is not very efficient since access to the elements of a sparse
1078 * matrix is slow. Rather, we first compute the contribution of each cell in
1079 * a small matrix with the degrees of freedom on the present cell, and only
1080 * transfer them to the global matrix when the computations are finished for
1081 * this cell. We do the same for the right hand side vector. So let's first
1082 * allocate these objects (these being local objects, all degrees of freedom
1083 * are coupling with all others, and we should use a full matrix object
1084 * rather than a sparse one for the local operations; everything will be
1085 * transferred to a global sparse matrix later on):
1086 *
1087 * @code
1088 *   FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
1089 *   Vector<double> cell_rhs(dofs_per_cell);
1090 *  
1091 * @endcode
1092 *
1093 * When assembling the contributions of each cell, we do this with the local
1094 * numbering of the degrees of freedom (i.e. the number running from zero
1095 * through dofs_per_cell-1). However, when we transfer the result into the
1096 * global matrix, we have to know the global numbers of the degrees of
1097 * freedom. When we query them, we need a scratch (temporary) array for
1098 * these numbers (see the discussion at the end of the introduction for
1099 * the type, types::global_dof_index, used here):
1100 *
1101 * @code
1102 *   std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1103 *  
1104 * @endcode
1105 *
1106 * Now for the loop over all cells. We have seen before how this works for a
1107 * triangulation. A DoFHandler has cell iterators that are exactly analogous
1108 * to those of a Triangulation, but with extra information about the degrees
1109 * of freedom for the finite element you're using. Looping over the active
1110 * cells of a degree-of-freedom handler works the same as for a triangulation.
1111 *
1112
1113 *
1114 * Note that we declare the type of the cell as `const auto &` instead of
1115 * `auto` this time around. In step 1, we were modifying the cells of the
1116 * triangulation by flagging them with refinement indicators. Here we're only
1117 * examining the cells without modifying them, so it's good practice to
1118 * declare `cell` as `const` in order to enforce this invariant.
1119 *
1120 * @code
1121 *   for (const auto &cell : dof_handler.active_cell_iterators())
1122 *   {
1123 * @endcode
1124 *
1125 * We are now sitting on one cell, and we would like the values and
1126 * gradients of the shape functions be computed, as well as the
1127 * determinants of the Jacobian matrices of the mapping between
1128 * reference cell and true cell, at the quadrature points. Since all
1129 * these values depend on the geometry of the cell, we have to have the
1130 * FEValues object re-compute them on each cell:
1131 *
1132 * @code
1133 *   fe_values.reinit(cell);
1134 *  
1135 * @endcode
1136 *
1137 * Next, reset the local cell's contributions to global matrix and
1138 * global right hand side to zero, before we fill them:
1139 *
1140 * @code
1141 *   cell_matrix = 0;
1142 *   cell_rhs = 0;
1143 *  
1144 * @endcode
1145 *
1146 * Now it is time to start integration over the cell, which we
1147 * do by looping over all quadrature points, which we will
1148 * number by q_index.
1149 *
1150 * @code
1151 *   for (const unsigned int q_index : fe_values.quadrature_point_indices())
1152 *   {
1153 * @endcode
1154 *
1155 * First assemble the matrix: For the Laplace problem, the
1156 * matrix on each cell is the integral over the gradients of
1157 * shape function i and j. Since we do not integrate, but
1158 * rather use quadrature, this is the sum over all
1159 * quadrature points of the integrands times the determinant
1160 * of the Jacobian matrix at the quadrature point times the
1161 * weight of this quadrature point. You can get the gradient
1162 * of shape function @f$i@f$ at quadrature point with number q_index by
1163 * using <code>fe_values.shape_grad(i,q_index)</code>; this
1164 * gradient is a 2-dimensional vector (in fact it is of type
1165 * Tensor@<1,dim@>, with here dim=2) and the product of two
1166 * such vectors is the scalar product, i.e. the product of
1167 * the two shape_grad function calls is the dot
1168 * product. This is in turn multiplied by the Jacobian
1169 * determinant and the quadrature point weight (that one
1170 * gets together by the call to FEValues::JxW() ). Finally,
1171 * this is repeated for all shape functions @f$i@f$ and @f$j@f$:
1172 *
1173 * @code
1174 *   for (const unsigned int i : fe_values.dof_indices())
1175 *   for (const unsigned int j : fe_values.dof_indices())
1176 *   cell_matrix(i, j) +=
1177 *   (fe_values.shape_grad(i, q_index) * // grad phi_i(x_q)
1178 *   fe_values.shape_grad(j, q_index) * // grad phi_j(x_q)
1179 *   fe_values.JxW(q_index)); // dx
1180 *  
1181 * @endcode
1182 *
1183 * We then do the same thing for the right hand side. Here,
1184 * the integral is over the shape function i times the right
1185 * hand side function, which we choose to be the function
1186 * with constant value one (more interesting examples will
1187 * be considered in the following programs).
1188 *
1189 * @code
1190 *   for (const unsigned int i : fe_values.dof_indices())
1191 *   cell_rhs(i) += (fe_values.shape_value(i, q_index) * // phi_i(x_q)
1192 *   1. * // f(x_q)
1193 *   fe_values.JxW(q_index)); // dx
1194 *   }
1195 * @endcode
1196 *
1197 * Now that we have the contribution of this cell, we have to transfer
1198 * it to the global matrix and right hand side. To this end, we first
1199 * have to find out which global numbers the degrees of freedom on this
1200 * cell have. Let's simply ask the cell for that information:
1201 *
1202 * @code
1203 *   cell->get_dof_indices(local_dof_indices);
1204 *  
1205 * @endcode
1206 *
1207 * Then again loop over all shape functions i and j and transfer the
1208 * local elements to the global matrix. The global numbers can be
1209 * obtained using local_dof_indices[i]:
1210 *
1211 * @code
1212 *   for (const unsigned int i : fe_values.dof_indices())
1213 *   for (const unsigned int j : fe_values.dof_indices())
1214 *   system_matrix.add(local_dof_indices[i],
1215 *   local_dof_indices[j],
1216 *   cell_matrix(i, j));
1217 *  
1218 * @endcode
1219 *
1220 * And again, we do the same thing for the right hand side vector.
1221 *
1222 * @code
1223 *   for (const unsigned int i : fe_values.dof_indices())
1224 *   system_rhs(local_dof_indices[i]) += cell_rhs(i);
1225 *   }
1226 *  
1227 *  
1228 * @endcode
1229 *
1230 * Now almost everything is set up for the solution of the discrete
1231 * system. However, we have not yet taken care of boundary values (in fact,
1232 * Laplace's equation without Dirichlet boundary values is not even uniquely
1233 * solvable, since you can add an arbitrary constant to the discrete
1234 * solution). We therefore have to do something about the situation.
1235 *
1236
1237 *
1238 * For this, we first obtain a list of the degrees of freedom on the
1239 * boundary and the value the shape function shall have there. For
1240 * simplicity, we only interpolate the boundary value function, rather than
1241 * projecting it onto the boundary. There is a function in the library which
1242 * does exactly this: VectorTools::interpolate_boundary_values(). Its
1243 * parameters are (omitting parameters for which default values exist and
1244 * that we don't care about): the DoFHandler object to get the global
1245 * numbers of the degrees of freedom on the boundary; the component of the
1246 * boundary where the boundary values shall be interpolated; the boundary
1247 * value function itself; and the output object.
1248 *
1249
1250 *
1251 * The component of the boundary is meant as follows: in many cases, you may
1252 * want to impose certain boundary values only on parts of the boundary. For
1253 * example, you may have inflow and outflow boundaries in fluid dynamics, or
1254 * clamped and free parts of bodies in deformation computations of
1255 * bodies. Then you will want to denote these different parts of the
1256 * boundary by indicators, and tell the interpolate_boundary_values
1257 * function to only compute the boundary values on a certain part of the
1258 * boundary (e.g. the clamped part, or the inflow boundary). By default,
1259 * all boundaries have a 0 boundary indicator, unless otherwise specified.
1260 * (For example, many functions in namespace GridGenerator specify otherwise.)
1261 * If sections of the boundary have different boundary conditions, you have to
1262 * number those parts with different boundary indicators. The function call
1263 * below will then only determine boundary values for those parts of the
1264 * boundary for which the boundary indicator is in fact the zero specified as
1265 * the second argument.
1266 *
1267
1268 *
1269 * The function describing the boundary values is an object of type Function
1270 * or of a derived class. One of the derived classes is
1271 * Functions::ZeroFunction, which describes (not unexpectedly) a function
1272 * which is zero everywhere. We create such an object in-place and pass it to
1274 *
1275
1276 *
1277 * Finally, the output object is a list of pairs of global degree of freedom
1278 * numbers (i.e. the number of the degrees of freedom on the boundary) and
1279 * their boundary values (which are zero here for all entries). This mapping
1280 * of DoF numbers to boundary values is done by the <code>std::map</code>
1281 * class.
1282 *
1283 * @code
1284 *   std::map<types::global_dof_index, double> boundary_values;
1285 *   VectorTools::interpolate_boundary_values(dof_handler,
1286 *   types::boundary_id(0),
1287 *   Functions::ZeroFunction<2>(),
1288 *   boundary_values);
1289 * @endcode
1290 *
1291 * Now that we got the list of boundary DoFs and their respective boundary
1292 * values, let's use them to modify the system of equations
1293 * accordingly. This is done by the following function call:
1294 *
1295 * @code
1296 *   MatrixTools::apply_boundary_values(boundary_values,
1297 *   system_matrix,
1298 *   solution,
1299 *   system_rhs);
1300 *   }
1301 *  
1302 *  
1303 * @endcode
1304 *
1305 *
1306 * <a name="step_3-Step3solve"></a>
1307 * <h4>Step3::solve</h4>
1308 *
1309
1310 *
1311 * The following function solves the discretized equation. As discussed in
1312 * the introduction, we want to use an iterative solver to do this,
1313 * specifically the Conjugate Gradient (CG) method.
1314 *
1315
1316 *
1317 * The way to do this in deal.II is a three-step process:
1318 * - First, we need to have an object that knows how to tell the CG algorithm
1319 * when to stop. This is done by using a SolverControl object, and as
1320 * stopping criterion we say: stop after a maximum of 1000 iterations (which
1321 * is far more than is needed for 1089 variables; see the results section to
1322 * find out how many were really used), and stop if the norm of the residual
1323 * is below @f$\tau=10^{-6}\|\mathbf b\|@f$ where @f$\mathbf b@f$ is the right hand
1324 * side vector. In practice, this latter criterion will be the one
1325 * which stops the iteration.
1326 * - Then we need the solver itself. The template parameter to the SolverCG
1327 * class is the type of the vectors we are using.
1328 * - The last step is to actually solve the system of equations. The CG solver
1329 * takes as arguments the components of the linear system @f$Ax=b@f$ (in the
1330 * order in which they appear in this equation), and a preconditioner
1331 * as the fourth argument. We don't feel ready to delve into preconditioners
1332 * yet, so we tell it to use the identity operation as preconditioner. Later
1333 * tutorial programs will spend significant amount of time and space on
1334 * constructing better preconditioners.
1335 *
1336
1337 *
1338 * At the end of this process, the `solution` variable contains the
1339 * nodal values of the solution function. At the end of the function, we
1340 * output how many Conjugate Gradients iterations it took to solve the
1341 * linear system.
1342 *
1343 * @code
1344 *   void Step3::solve()
1345 *   {
1346 *   SolverControl solver_control(1000, 1e-6 * system_rhs.l2_norm());
1347 *   SolverCG<Vector<double>> solver(solver_control);
1348 *   solver.solve(system_matrix, solution, system_rhs, PreconditionIdentity());
1349 *  
1350 *   std::cout << solver_control.last_step()
1351 *   << " CG iterations needed to obtain convergence." << std::endl;
1352 *   }
1353 *  
1354 *  
1355 * @endcode
1356 *
1357 *
1358 * <a name="step_3-Step3output_results"></a>
1359 * <h4>Step3::output_results</h4>
1360 *
1361
1362 *
1363 * The last part of a typical finite element program is to output the results
1364 * and maybe do some postprocessing (for example compute the maximal stress
1365 * values at the boundary, or the average flux across the outflow, etc). We
1366 * have no such postprocessing here, but we would like to write the solution
1367 * to a file.
1368 *
1369 * @code
1370 *   void Step3::output_results() const
1371 *   {
1372 * @endcode
1373 *
1374 * To write the output to a file, we need an object which knows about output
1375 * formats and the like. This is the DataOut class, and we need an object of
1376 * that type:
1377 *
1378 * @code
1379 *   DataOut<2> data_out;
1380 * @endcode
1381 *
1382 * Now we have to tell it where to take the values from which it shall
1383 * write. We tell it which DoFHandler object to use, and the solution vector
1384 * (and the name by which the solution variable shall appear in the output
1385 * file). If we had more than one vector which we would like to look at in
1386 * the output (for example right hand sides, errors per cell, etc) we would
1387 * add them as well:
1388 *
1389 * @code
1390 *   data_out.attach_dof_handler(dof_handler);
1391 *   data_out.add_data_vector(solution, "solution");
1392 * @endcode
1393 *
1394 * After the DataOut object knows which data it is to work on, we have to
1395 * tell it to process them into something the back ends can handle. The
1396 * reason is that we have separated the frontend (which knows about how to
1397 * treat DoFHandler objects and data vectors) from the back end (which knows
1398 * many different output formats) and use an intermediate data format to
1399 * transfer data from the front- to the backend. The data is transformed
1400 * into this intermediate format by the following function:
1401 *
1402 * @code
1403 *   data_out.build_patches();
1404 *  
1405 * @endcode
1406 *
1407 * Now we have everything in place for the actual output. Just open a file
1408 * and write the data into it, using VTK format (there are many other
1409 * functions in the DataOut class we are using here that can write the
1410 * data in postscript, AVS, GMV, Gnuplot, or some other file
1411 * formats):
1412 *
1413 * @code
1414 *   const std::string filename = "solution.vtk";
1415 *   std::ofstream output(filename);
1416 *   data_out.write_vtk(output);
1417 *   std::cout << "Output written to " << filename << std::endl;
1418 *   }
1419 *  
1420 *  
1421 * @endcode
1422 *
1423 *
1424 * <a name="step_3-Step3run"></a>
1425 * <h4>Step3::run</h4>
1426 *
1427
1428 *
1429 * Finally, the last function of this class is the main function which calls
1430 * all the other functions of the <code>Step3</code> class. The order in which
1431 * this is done resembles the order in which most finite element programs
1432 * work. Since the names are mostly self-explanatory, there is not much to
1433 * comment about:
1434 *
1435 * @code
1436 *   void Step3::run()
1437 *   {
1438 *   make_grid();
1439 *   setup_system();
1440 *   assemble_system();
1441 *   solve();
1442 *   output_results();
1443 *   }
1444 *  
1445 *  
1446 * @endcode
1447 *
1448 *
1449 * <a name="step_3-Thecodemaincodefunction"></a>
1450 * <h3>The <code>main</code> function</h3>
1451 *
1452
1453 *
1454 * This is the main function of the program. Since the concept of a
1455 * main function is mostly a remnant from the pre-object oriented era
1456 * before C++ programming, it often does not do much more than
1457 * creating an object of the top-level class and calling its principle
1458 * function.
1459 *
1460 * @code
1461 *   int main()
1462 *   {
1463 *   Step3 laplace_problem;
1464 *   laplace_problem.run();
1465 *  
1466 *   return 0;
1467 *   }
1468 * @endcode
1469<a name="step_3-Results"></a><h1>Results</h1>
1470
1471
1472The output of the program looks as follows:
1473@code
1474Number of active cells: 1024
1475Number of degrees of freedom: 1089
147636 CG iterations needed to obtain convergence.
1477Output written to solution.vtk
1478@endcode
1479
1480The last line is the output we generated at the bottom of the
1481`output_results()` function: The program generated the file
1482<code>solution.vtk</code>, which is in the VTK format that is widely
1483used by many visualization programs today -- including the two
1484heavy-weights <a href="https://www.llnl.gov/visit">VisIt</a> and
1485<a href="https://www.paraview.org">Paraview</a> that are the most
1486commonly used programs for this purpose today.
1487
1488Using VisIt, it is not very difficult to generate a picture of the
1489solution like this:
1490<table width="60%" align="center">
1491 <tr>
1492 <td align="center">
1493 <img src="https://dealii.org/images/steps/developer/step-3.solution-3.png" alt="Visualization of the solution of step-3">
1494 </td>
1495 </tr>
1496</table>
1497It shows both the solution and the mesh, elevated above the @f$x@f$-@f$y@f$ plane
1498based on the value of the solution at each point. Of course the solution
1499here is not particularly exciting, but that is a result of both what the
1500Laplace equation represents and the right hand side @f$f(\mathbf x)=1@f$ we
1501have chosen for this program: The Laplace equation describes (among many
1502other uses) the vertical deformation of a membrane subject to an external,
1503vertical force. In the current example, the membrane's borders
1504are clamped to a square frame with no vertical variation; a constant
1505force density will therefore intuitively lead to a membrane that
1506simply bulges upward -- like the one shown above.
1507
1508VisIt and Paraview both allow playing with various kinds of visualizations
1509of the solution. Several video lectures show how to use these programs.
1510See also <a href="https://www.math.colostate.edu/~bangerth/videos.676.11.html">video lecture 11</a>, <a href="https://www.math.colostate.edu/~bangerth/videos.676.32.html">video lecture 32</a>.
1511
1512
1513
1514@anchor step_3-Extensions
1515<a name="step_3-Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
1516
1517
1518If you want to play around a little bit with this program, here are a few
1519suggestions:
1520</p>
1521
1522<ul>
1523 <li>
1524 Change the geometry and mesh: In the program, we have generated a square
1525 domain and mesh by using the GridGenerator::hyper_cube()
1526 function. However, the <code>GridGenerator</code> has a good number of other
1527 functions as well. Try an L-shaped domain, a ring, or other domains you find
1528 there.
1529 </li>
1530
1531 <li>
1532 Change the boundary condition: The code uses the Functions::ZeroFunction
1533 function to generate zero boundary conditions. However, you may want to try
1534 non-zero constant boundary values using
1535 <code>Functions::ConstantFunction&lt;2&gt;(1)</code> instead of
1536 <code>Functions::ZeroFunction&lt;2&gt;()</code> to have unit Dirichlet boundary
1537 values. More exotic functions are described in the documentation of the
1538 Functions namespace, and you may pick one to describe your particular boundary
1539 values.
1540 </li>
1541
1542 <li> Modify the type of boundary condition: Presently, what happens
1543 is that we use Dirichlet boundary values all around, since the
1544 default is that all boundary parts have boundary indicator zero, and
1545 then we tell the
1546 VectorTools::interpolate_boundary_values() function to
1547 interpolate boundary values to zero on all boundary components with
1548 indicator zero. <p> We can change this behavior if we assign parts
1549 of the boundary different indicators. For example, try this
1550 immediately after calling GridGenerator::hyper_cube():
1551 @code
1552 triangulation.begin_active()->face(0)->set_boundary_id(1);
1553 @endcode
1554
1555 What this does is it first asks the triangulation to
1556 return an iterator that points to the first active cell. Of course,
1557 this being the coarse mesh for the triangulation of a square, the
1558 triangulation has only a single cell at this moment, and it is
1559 active. Next, we ask the cell to return an iterator to its first
1560 face, and then we ask the face to reset the boundary indicator of
1561 that face to 1. What then follows is this: When the mesh is refined,
1562 faces of child cells inherit the boundary indicator of their
1563 parents, i.e. even on the finest mesh, the faces on one side of the
1564 square have boundary indicator 1. Later, when we get to
1565 interpolating boundary conditions, the
1566 VectorTools::interpolate_boundary_values() call will only produce boundary
1567 values for those faces that have zero boundary indicator, and leave
1568 those faces alone that have a different boundary indicator. What
1569 this then does is to impose Dirichlet boundary conditions on the
1570 former, and homogeneous Neumann conditions on the latter (i.e. zero
1571 normal derivative of the solution, unless one adds additional terms
1572 to the right hand side of the variational equality that deal with
1573 potentially non-zero Neumann conditions). You will see this if you
1574 run the program.
1575
1576 An alternative way to change the boundary indicator is to label
1577 the boundaries based on the Cartesian coordinates of the face centers.
1578 For example, we can label all of the cells along the top and
1579 bottom boundaries with a boundary indicator 1 by checking to
1580 see if the cell centers' y-coordinates are within a tolerance
1581 (here `1e-12`) of -1 and 1. Try this immediately after calling
1582 GridGenerator::hyper_cube(), as before:
1583 @code
1584 for (auto &face : triangulation.active_face_iterators())
1585 if (face->at_boundary())
1586 if (std::fabs(face->center()(1) - (-1.0)) < 1e-12 ||
1587 std::fabs(face->center()(1) - (1.0)) < 1e-12)
1588 face->set_boundary_id(1);
1589 @endcode
1590 Although this code is a bit longer than before, it is useful for
1591 complex geometries, as it does not require knowledge of face labels.
1592
1593 <li>
1594 A slight variation of the last point would be to set different boundary
1595 values as above, but then use a different boundary value function for
1596 boundary indicator one. In practice, what you have to do is to add a second
1597 call to <code>interpolate_boundary_values</code> for boundary indicator one:
1598 @code
1599 VectorTools::interpolate_boundary_values(dof_handler,
1600 types::boundary_id(1),
1601 Functions::ConstantFunction<2>(1.),
1602 boundary_values);
1603 @endcode
1604 If you have this call immediately after the first one to this function, then
1605 it will interpolate boundary values on faces with boundary indicator 1 to the
1606 unit value, and merge these interpolated values with those previously
1607 computed for boundary indicator 0. The result will be that we will get
1608 discontinuous boundary values, zero on three sides of the square, and one on
1609 the fourth.
1610
1611 <li>
1612 Use triangles: As mentioned in the results section of @ref step_1 "step-1", for
1613 historical reasons, almost all tutorial programs for deal.II are
1614 written using quadrilateral or hexahedral meshes. But deal.II also
1615 supports triangular and tetrahedral meshes. So a good experiment would
1616 be to replace the mesh used here by a triangular mesh.
1617
1618 This is *almost* trivial. First, as discussed in @ref step_1 "step-1", we may want
1619 to start with the quadrilateral mesh we are already creating, and
1620 then convert it into a triangular one. You can do that by replacing
1621 the first line of `Step3::make_grid()` by the following code:
1622 @code
1623 Triangulation<2> triangulation_quad;
1624 GridGenerator::hyper_cube(triangulation_quad, -1, 1);
1626 triangulation);
1627 @endcode
1629 quadrilateral by eight triangles with half the diameter of the original
1630 quadrilateral; as a consequence, the resulting mesh is substantially
1631 finer and one might expect that the solution is consequently more
1632 accurate (but also has many more degrees of freedom). That is a question
1633 you can explore with the techniques discussed in the "Results" section
1634 of @ref step_4 "step-4", but that goes beyond what we want to demonstrate here.
1635
1636 If you run this program, you will run into an error message that
1637 will look something like this:
1638 @code
1639--------------------------------------------------------
1640An error occurred in line <2633> of file </home/bangerth/p/deal.II/1/dealii/include/deal.II/dofs/dof_accessor.templates.h> in function
1641 const ::FiniteElement<dimension_, space_dimension_>& ::DoFCellAccessor<dim, spacedim, lda>::get_fe() const [with int dimension_ = 2; int space_dimension_ = 2; bool level_dof_access = false]
1642The violated condition was:
1643 this->reference_cell() == fe.reference_cell()
1644Additional information:
1645 The reference-cell type used on this cell (Tri) does not match the
1646 reference-cell type of the finite element associated with this cell
1647 (Quad). Did you accidentally use simplex elements on hypercube meshes
1648 (or the other way around), or are you using a mixed mesh and assigned
1649 a simplex element to a hypercube cell (or the other way around) via
1650 the active_fe_index?
1651 @endcode
1652 It is worth carefully reading the error message. It doesn't just
1653 state that there is an error, but also how it may have
1654 arisen. Specifically, it asks whether we are using a finite element
1655 for simplex meshes (in 2d simplices are triangles) with a hypercube
1656 mesh (in 2d hypercubes are quadrilaterals) or the other way around?
1657
1658 Of course, this is exactly what we are doing, though this may
1659 perhaps not be clear to you. But if you look up the documentation,
1660 you will find that the FE_Q element we use in the main class can
1661 only be used on hypercube meshes; what we *want* to use instead now
1662 that we are using a simplex mesh is the FE_SimplexP class that is the
1663 equivalent to FE_Q for simplex cells. (To do this, you will also
1664 have to add `#include <deal.II/fe/fe_simplex_p.h>` at the top of the file.)
1665
1666 The last thing you need to change (which at the time of writing is
1667 unfortunately not prompted by getting an error message) is that when
1668 we integrate, we need to use a quadrature formula that is
1669 appropriate for triangles. This is done by changing QGauss by
1670 QGaussSimplex in the code.
1671
1672 With all of these steps, you then get the following solution:
1673 <img src="https://dealii.org/images/steps/developer/step-3.solution-triangles.png" alt="Visualization of the solution of step-3 using triangles">
1674
1675 <li>
1676 Verify correctness and observe convergence: When you compute something,
1677 your next step should be to make sure it is actually correct. That's
1678 probably good life advice anyway, but it is a particularly useful skill in scientific
1679 computing. In the current context, it is made more difficult by the fact
1680 that we do not actually know what the exact solution to the Laplace
1681 equation with the given right hand is. In the @ref step_7 "step-7" tutorial program,
1682 we will come back to this issue and come up with ways to *choose* the
1683 right hand side in a way so that we know what the exact solution is; we can
1684 then compare the computed (approximate) solution against this known (exact) solution.
1685
1686 Here, we can at least check one aspect of a correct implementation:
1687 If we make the mesh finer and finer, finite element theory tells us
1688 the solution needs to *converge* to the exact solution. Because we
1689 don't know what the exact solution is, so for the moment all we can
1690 say is that the computed solutions need to converge to *something*
1691 as the mesh is refined. This is true for the solution as a function
1692 of @f$x,y@f$, and it is specifically also true for the solution at a
1693 particular point. This is easy to check here: We can evaluate the
1694 value of the solution at a
1695 single point and compare the value for different %numbers of global
1696 refinement (the number of global refinement steps is set in
1697 <code>LaplaceProblem::make_grid()</code> above). To evaluate the
1698 solution at a point, say at @f$(\frac 13, \frac 13)@f$, we could add the
1699 following code to the <code>LaplaceProblem::output_results</code> function:
1700 @code
1701 std::cout << "Solution at (1/3,1/3): "
1702 << VectorTools::point_value(dof_handler, solution,
1703 Point<2>(1./3, 1./3))
1704 << std::endl;
1705 @endcode
1706 For 1 through 9 global refinement steps, we then get the following sequence
1707 of point values:
1708
1709 <table align="center" class="doxtable">
1710 <tr> <th># of refinements</th> <th>@f$u_h(\frac 13,\frac13)@f$</th> </tr>
1711 <tr> <td>1</td> <td>0.166667</td> </tr>
1712 <tr> <td>2</td> <td>0.227381</td> </tr>
1713 <tr> <td>3</td> <td>0.237375</td> </tr>
1714 <tr> <td>4</td> <td>0.240435</td> </tr>
1715 <tr> <td>5</td> <td>0.241140</td> </tr>
1716 <tr> <td>6</td> <td>0.241324</td> </tr>
1717 <tr> <td>7</td> <td>0.241369</td> </tr>
1718 <tr> <td>8</td> <td>0.241380</td> </tr>
1719 <tr> <td>9</td> <td>0.241383</td> </tr>
1720 </table>
1721
1722 By noticing that the difference between each two consecutive values reduces
1723 by about a factor of 4, we can conjecture that the "correct" value may be
1724 @f$u(\frac 13, \frac 13)\approx 0.241384@f$. In fact, if we assumed this to be
1725 the correct value, we could show that the sequence above indeed shows @f${\cal
1726 O}(h^2)@f$ convergence &mdash; theoretically, the convergence order should be
1727 @f${\cal O}(h^2 |\log h|)@f$ (because that's the theoretical error bound in the
1728 @f$L_\infty@f$ norm) but the symmetry of the domain and the mesh may lead
1729 to the better convergence order observed.
1730
1731 A slight variant of this would be to repeat the test with quadratic
1732 elements. All you need to do is to set the polynomial degree of the finite
1733 element to two in the constructor
1734 <code>LaplaceProblem::LaplaceProblem</code>.
1735
1736 <li>Convergence of the mean: A different way to see that the solution
1737 actually converges (to something) is to compute the mean of the solution.
1738 To this end, add
1739 the following code to <code>LaplaceProblem::output_results</code>:
1740 @code
1741 std::cout << "Mean value: "
1742 << VectorTools::compute_mean_value (dof_handler,
1743 QGauss<2>(fe.degree + 1),
1744 solution,
1745 0)
1746 << std::endl;
1747 @endcode
1748 The documentation of the VectorTools::compute_mean_value() function
1749 explains what the second and fourth
1750 parameters mean, while the first and third should be obvious. Doing the same
1751 study again where we change the number of global refinement steps, we get
1752 the following result:
1753
1754 <table align="center" class="doxtable">
1755 <tr> <th># of refinements</th> <th>@f$\int_\Omega u_h(x)\; dx@f$</th> </tr>
1756 <tr> <td>0</td> <td>0.09375000</td> </tr>
1757 <tr> <td>1</td> <td>0.12790179</td> </tr>
1758 <tr> <td>2</td> <td>0.13733440</td> </tr>
1759 <tr> <td>3</td> <td>0.13976069</td> </tr>
1760 <tr> <td>4</td> <td>0.14037251</td> </tr>
1761 <tr> <td>5</td> <td>0.14052586</td> </tr>
1762 <tr> <td>6</td> <td>0.14056422</td> </tr>
1763 <tr> <td>7</td> <td>0.14057382</td> </tr>
1764 <tr> <td>8</td> <td>0.14057622</td> </tr>
1765 </table>
1766
1767 Again, the difference between two adjacent values goes down by about a
1768 factor of four, indicating convergence as @f${\cal O}(h^2)@f$.
1769</ul>
1770
1771
1772
1773<a name="step_3-UsingHDF5tooutputthesolutionandadditionaldata"></a><h3>Using %HDF5 to output the solution and additional data</h3>
1774
1775
1776%HDF5 is a commonly used format that can be read by many scripting
1777languages (e.g. R or Python). It is not difficult to get deal.II to
1778produce some %HDF5 files that can then be used in external scripts to
1779postprocess some of the data generated by this program. Here are some
1780ideas on what is possible.
1781
1782
1783<a name="step_3-Changingtheoutputtoh5"></a><h4> Changing the output to .h5</h4>
1784
1785
1786To fully make use of the automation we first need to introduce a private variable for the number of
1787global refinement steps <code>unsigned int n_refinement_steps </code>, which will be used for the output filename.
1788In <code>make_grid()</code> we then replace <code>triangulation.refine_global(5);</code> with
1789@code
1790n_refinement_steps = 5;
1791triangulation.refine_global(n_refinement_steps);
1792@endcode
1793The deal.II library has two different %HDF5 bindings, one in the HDF5
1794namespace (for interfacing to general-purpose data files)
1795and another one in DataOut (specifically for writing files for the
1796visualization of solutions).
1797Although the HDF5 deal.II binding supports both serial and MPI, the %HDF5 DataOut binding
1798only supports parallel output.
1799For this reason we need to initialize an MPI
1800communicator with only one processor. This is done by adding the following code.
1801@code
1802int main(int argc, char* argv[])
1803{
1804 Utilities::MPI::MPI_InitFinalize mpi_initialization(argc, argv, 1);
1805 ...
1806}
1807@endcode
1808Next we change the `Step3::output_results()` output routine as
1809described in the DataOutBase namespace documentation:
1810@code
1811const std::string filename_h5 = "solution_" + std::to_string(n_refinement_steps) + ".h5";
1812DataOutBase::DataOutFilterFlags flags(true, true);
1813DataOutBase::DataOutFilter data_filter(flags);
1814data_out.write_filtered_data(data_filter);
1815data_out.write_hdf5_parallel(data_filter, filename_h5, MPI_COMM_WORLD);
1816@endcode
1817The resulting file can then be visualized just like the VTK file that
1818the original version of the tutorial produces; but, since %HDF5 is a
1819more general file format, it can also easily be processed in scripting
1820languages for other purposes.
1821
1822
1823<a name="step_3-Addingthepointvalueandthemeanseeextensionaboveintotheh5file"></a><h4> Adding the point value and the mean (see extension above) into the .h5 file</h4>
1824
1825
1826After outputting the solution, the file can be opened again to include
1827more datasets. This allows us to keep all the necessary information
1828of our experiment in a single result file, which can then be read and
1829processed by some postprocessing script.
1830(Have a look at HDF5::Group::write_dataset() for further
1831information on the possible output options.)
1832
1833To make this happen, we first include the necessary header into our file :
1834@code
1835#include <deal.II/base/hdf5.h>
1836@endcode
1837Adding the following lines to the end
1838of our output routine adds the information about the value of the
1839solution at a particular point, as well as the mean value of the
1840solution, to our %HDF5 file :
1841@code
1842HDF5::File data_file(filename_h5, HDF5::File::FileAccessMode::open, MPI_COMM_WORLD);
1843Vector<double> point_value(1);
1844point_value[0] = VectorTools::point_value(dof_handler, solution,
1845 Point<2>(1./3, 1./3));
1846data_file.write_dataset("point_value", point_value);
1847Vector<double> mean_value(1);
1848mean_value[0] = VectorTools::compute_mean_value(dof_handler,
1849 QGauss<2>(fe.degree + 1),
1850 solution, 0);
1851data_file.write_dataset("mean_value",mean_value);
1852@endcode
1853
1854
1855
1856<a name="step_3-UsingRandggplot2togenerateplots"></a><h3> Using R and ggplot2 to generate plots</h3>
1857
1858@note Alternatively, one could use the Python code in the next subsection.
1859
1860The data put into %HDF5 files above can then be used from scripting
1861languages for further postprocessing. In the following, let us show
1862how this can, in particular, be done with the
1863<a href="https://en.wikipedia.org/wiki/R_(programming_language)">R
1864programming language</a>, a widely used language in statistical data
1865analysis. (Similar things can also be done in Python, for example.)
1866If you are unfamiliar with R and ggplot2 you could check out the data carpentry course on R
1867<a href="https://datacarpentry.org/R-ecology-lesson/index.html">here</a>.
1868Furthermore, since most search engines struggle with searches of the form "R + topic",
1869we recommend using the specializes service <a
1870href="http://rseek.org">RSeek </a> instead.
1871
1872The most prominent difference between R and other languages is that
1873the assignment operator (`a = 5`) is typically written as
1874`a <- 5`. As the latter is considered standard we will use it in our examples as well.
1875To open the `.h5` file in R you have to install the <a href="https://bioconductor.org/packages/release/bioc/html/rhdf5.html">rhdf5</a> package, which is a part of the Bioconductor package.
1876
1877First we will include all necessary packages and have a look at how the data is structured in our file.
1878@code{.r}
1879library(rhdf5) # library for handling HDF5 files
1880library(ggplot2) # main plotting library
1881library(grDevices) # needed for output to PDF
1882library(viridis) # contains good colormaps for sequential data
1883
1884refinement <- 5
1885h5f <- H5Fopen(paste("solution_",refinement,".h5",sep=""))
1886print(h5f)
1887@endcode
1888This gives the following output
1889@code{.unparsed}
1890HDF5 FILE
1891 name /
1892filename
1893
1894 name otype dclass dim
18950 cells H5I_DATASET INTEGER x 1024
18961 mean_value H5I_DATASET FLOAT 1
18972 nodes H5I_DATASET FLOAT x 1089
18983 point_value H5I_DATASET FLOAT 1
18994 solution H5I_DATASET FLOAT x 1089
1900@endcode
1901The datasets can be accessed by <code>h5f\$name</code>. The function
1902<code>dim(h5f\$cells)</code> gives us the dimensions of the matrix
1903that is used to store our cells.
1904We can see the following three matrices, as well as the two
1905additional data points we added.
1906<ul>
1907<li> <code>cells</code>: a 4x1024 matrix that stores the (C++) vertex indices for each cell
1908<li> <code>nodes</code>: a 2x1089 matrix storing the position values (x,y) for our cell vertices
1909<li> <code>solution</code>: a 1x1089 matrix storing the values of our solution at each vertex
1910</ul>
1911Now we can use this data to generate various plots. Plotting with ggplot2 usually splits into two steps.
1912At first the data needs to be manipulated and added to a <code>data.frame</code>.
1913After that, a <code>ggplot</code> object is constructed and manipulated by adding plot elements to it.
1914
1915<code>nodes</code> and <code>cells</code> contain all the information we need to plot our grid.
1916The following code wraps all the data into one dataframe for plotting our grid:
1917@code{.r}
1918# Counting in R starts at 1 instead of 0, so we need to increment all
1919# vertex indices by one:
1920cell_ids <- h5f$cells+1
1921
1922# Store the x and y positions of each vertex in one big vector in a
1923# cell by cell fashion (every 4 entries belong to one cell):
1924cells_x <- h5f$nodes[1,][cell_ids]
1925cells_y <- h5f$nodes[2,][cell_ids]
1926
1927# Construct a vector that stores the matching cell by cell grouping
1928# (1,1,1,1,2,2,2,2,...):
1929groups <- rep(1:ncol(cell_ids),each=4)
1930
1931# Finally put everything into one dataframe:
1932meshdata <- data.frame(x = cells_x, y = cells_y, id = groups)
1933@endcode
1934
1935With the finished dataframe we have everything we need to plot our grid:
1936@code{.r}
1937pdf (paste("grid_",refinement,".pdf",sep=""),width = 5,height = 5) # Open new PDF file
1938plt <- ggplot(meshdata,aes(x=x,y=y,group=id)) # Construction of our plot
1939 # object, at first only data
1940
1941plt <- plt + geom_polygon(fill="white",colour="black") # Actual plotting of the grid as polygons
1942plt <- plt + ggtitle(paste("grid at refinement level #",refinement))
1943
1944print(plt) # Show the current state of the plot/add it to the pdf
1945dev.off() # Close PDF file
1946@endcode
1947
1948The contents of this file then look as follows (not very exciting, but
1949you get the idea):
1950<table width="60%" align="center">
1951 <tr>
1952 <td align="center">
1953 <img src="https://dealii.org/images/steps/developer/step-3.extensions.grid_5.png" alt="Grid after 5 refinement steps of step-3">
1954 </td>
1955 </tr>
1956</table>
1957
1958We can also visualize the solution itself, and this is going to look
1959more interesting.
1960To make a 2D pseudocolor plot of our solution we will use <code>geom_raster</code>.
1961This function needs a structured grid, i.e. uniform in x and y directions.
1962Luckily our data at this point is structured in the right way.
1963The following code plots a pseudocolor representation of our surface into a new PDF:
1964@code{.r}
1965pdf (paste("pseudocolor_",refinement,".pdf",sep=""),width = 5,height = 4.2) # Open new PDF file
1966colordata <- data.frame(x = h5f$nodes[1,],y = h5f$nodes[2,] , solution = h5f$solution[1,])
1967plt <- ggplot(colordata,aes(x=x,y=y,fill=solution))
1968plt <- plt + geom_raster(interpolate=TRUE)
1969plt <- plt + scale_fill_viridis()
1970plt <- plt + ggtitle(paste("solution at refinement level #",refinement))
1971
1972print(plt)
1973dev.off()
1974H5Fclose(h5f) # Close the HDF5 file
1975@endcode
1976This is now going to look as follows:
1977<table width="60%" align="center">
1978 <tr>
1979 <td align="center">
1980 <img src="https://dealii.org/images/steps/developer/step-3.extensions.pseudocolor_5.png" alt="Solution after 5 refinement steps of step-3">
1981 </td>
1982 </tr>
1983</table>
1984
1985For plotting the convergence curves we need to re-run the C++ code multiple times with different values for <code>n_refinement_steps</code>
1986starting from 1.
1987Since every file only contains a single data point we need to loop over them and concatenate the results into a single vector.
1988@code{.r}
1989n_ref <- 8 # Maximum refinement level for which results are existing
1990
1991# First we initiate all vectors with the results of the first level
1992h5f <- H5Fopen("solution_1.h5")
1993dofs <- dim(h5f$solution)[2]
1994mean <- h5f$mean_value
1995point <- h5f$point_value
1996H5Fclose(h5f)
1997
1998for (reflevel in 2:n_ref)
1999{
2000 h5f <- H5Fopen(paste("solution_",reflevel,".h5",sep=""))
2001 dofs <- c(dofs,dim(h5f\$solution)[2])
2002 mean <- c(mean,h5f\$mean_value)
2003 point <- c(point,h5f\$point_value)
2004 H5Fclose(h5f)
2005}
2006@endcode
2007As we are not interested in the values themselves but rather in the error compared to a "exact" solution we will
2008assume our highest refinement level to be that solution and omit it from the data.
2009@code{.r}
2010# Calculate the error w.r.t. our maximum refinement step
2011mean_error <- abs(mean[1:n_ref-1]-mean[n_ref])
2012point_error <- abs(point[1:n_ref-1]-point[n_ref])
2013
2014# Remove the highest value from our DoF data
2015dofs <- dofs[1:n_ref-1]
2016convdata <- data.frame(dofs = dofs, mean_value= mean_error, point_value = point_error)
2017@endcode
2018Now we have all the data available to generate our plots.
2019It is often useful to plot errors on a log-log scale, which is
2020accomplished in the following code:
2021@code
2022pdf (paste("convergence.pdf",sep=""),width = 5,height = 4.2)
2023plt <- ggplot(convdata,mapping=aes(x = dofs, y = mean_value))
2024plt <- plt+geom_line()
2025plt <- plt+labs(x="#DoFs",y = "mean value error")
2026plt <- plt+scale_x_log10()+scale_y_log10()
2027print(plt)
2028
2029plt <- ggplot(convdata,mapping=aes(x = dofs, y = point_value))
2030plt <- plt+geom_line()
2031plt <- plt+labs(x="#DoFs",y = "point value error")
2032plt <- plt+scale_x_log10()+scale_y_log10()
2033print(plt)
2034
2035dev.off()
2036@endcode
2037This results in the following plot that shows how the errors in the
2038mean value and the solution value at the chosen point nicely converge
2039to zero:
2040<table style="width:50%" align="center">
2041 <tr>
2042 <td><img src="https://dealii.org/images/steps/developer/step-3.extensions.convergence_mean.png" alt=""></td>
2043 <td><img src="https://dealii.org/images/steps/developer/step-3.extensions.convergence_point.png" alt=""></td>
2044 </tr>
2045</table>
2046
2047<a name="step_3-UsingPythontogenerateplots"></a><h3> Using Python to generate plots</h3>
2048
2049
2050In this section we discuss the postprocessing of the data stored in %HDF5 files using the "python" programming language.
2051The necessary packages to import are
2052@code{.py}
2053import numpy as np # to work with multidimensional arrays
2054import h5py # to work with %HDF5 files
2055
2056import pandas as pd # for data frames
2057import matplotlib.pyplot as plt # plotting
2058from matplotlib.patches import Polygon
2059
2060from scipy.interpolate import griddata # interpolation function
2061from matplotlib import cm # for colormaps
2062
2063@endcode
2064The %HDF5 solution file corresponding to `refinement = 5` can be opened as
2065@code{.py}
2066refinement = 5
2067filename = "solution_%d.h5" % refinement
2068file = h5py.File(filename, "r")
2069@endcode
2070The following prints out the tables in the %HDF5 file
2071@code{.py}
2072for key, value in file.items():
2073 print(key, " : ", value)
2074@endcode
2075which prints out
2076@code{.unparsed}
2077cells : <HDF5 dataset "cells": shape (1024, 4), type "<u4">
2078mean_value : <HDF5 dataset "mean_value": shape (1,), type "<f8">
2079nodes : <HDF5 dataset "nodes": shape (1089, 2), type "<f8">
2080point_value : <HDF5 dataset "point_value": shape (1,), type "<f8">
2081solution : <HDF5 dataset "solution": shape (1089, 1), type "<f8">
2082@endcode
2083There are @f$(32+1)\times(32+1) = 1089@f$ nodes.
2084The coordinates of these nodes @f$(x,y)@f$ are stored in the table `nodes` in the %HDF5 file.
2085There are a total of @f$32\times 32 = 1024@f$ cells. The nodes which make up each cell are
2086marked in the table `cells` in the %HDF5 file.
2087
2088Next, we extract the data into multidimensional arrays
2089@code{.py}
2090nodes = np.array(file["/nodes"])
2091cells = np.array(file["/cells"])
2092solution = np.array(file["/solution"])
2093
2094x, y = nodes.T
2095@endcode
2096
2097The following stores the @f$x@f$ and @f$y@f$ coordinates of each node of each cell in one flat array.
2098@code{.py}
2099cell_x = x[cells.flatten()]
2100cell_y = y[cells.flatten()]
2101@endcode
2102The following tags the cell ids. Each four entries correspond to one cell.
2103Then we collect the coordinates and ids into a data frame
2104@code{.py}
2105n_cells = cells.shape[0]
2106cell_ids = np.repeat(np.arange(n_cells), 4)
2107meshdata = pd.DataFrame({"x": cell_x, "y": cell_y, "ids": cell_ids})
2108@endcode
2109The data frame looks
2110@code{.unparsed}
2111print(meshdata)
2112
2113 x y ids
21140 0.00000 0.00000 0
21151 0.03125 0.00000 0
21162 0.03125 0.03125 0
21173 0.00000 0.03125 0
21184 0.03125 0.00000 1
2119... ... ... ...
21204091 0.93750 1.00000 1022
21214092 0.96875 0.96875 1023
21224093 1.00000 0.96875 1023
21234094 1.00000 1.00000 1023
21244095 0.96875 1.00000 1023
2125
21264096 rows × 3 columns
2127@endcode
2128
2129To plot the mesh, we loop over all cells and connect the first and last node to complete the polygon
2130@code{.py}
2131fig, ax = plt.subplots()
2132ax.set_aspect("equal", "box")
2133ax.set_title("grid at refinement level #%d" % refinement)
2134
2135for cell_id, cell in meshdata.groupby(["ids"]):
2136 cell = pd.concat([cell, cell.head(1)])
2137 plt.plot(cell["x"], cell["y"], c="k")
2138@endcode
2139Alternatively one could use the matplotlib built-in Polygon class
2140@code{.py}
2141fig, ax = plt.subplots()
2142ax.set_aspect("equal", "box")
2143ax.set_title("grid at refinement level #%d" % refinement)
2144for cell_id, cell in meshdata.groupby(["ids"]):
2145 patch = Polygon(cell[["x", "y"]], facecolor="w", edgecolor="k")
2146 ax.add_patch(patch)
2147@endcode
2148
2149To plot the solution, we first create a finer grid and then interpolate the solution values
2150into the grid and then plot it.
2151@code{.py}
2152nx = int(np.sqrt(n_cells)) + 1
2153nx *= 10
2154xg = np.linspace(x.min(), x.max(), nx)
2155yg = np.linspace(y.min(), y.max(), nx)
2156
2157xgrid, ygrid = np.meshgrid(xg, yg)
2158solution_grid = griddata((x, y), solution.flatten(), (xgrid, ygrid), method="linear")
2159
2160fig = plt.figure()
2161ax = fig.add_subplot(1, 1, 1)
2162ax.set_title("solution at refinement level #%d" % refinement)
2163c = ax.pcolor(xgrid, ygrid, solution_grid, cmap=cm.viridis)
2164fig.colorbar(c, ax=ax)
2165
2166plt.show()
2167@endcode
2168
2169To check the convergence of `mean_value` and `point_value`
2170we loop over data of all refinements and store into vectors <code>mean_values</code> and <code>mean_values</code>
2171@code{.py}
2172mean_values = np.zeros((8,))
2173point_values = np.zeros((8,))
2174dofs = np.zeros((8,))
2175
2176for refinement in range(1, 9):
2177 filename = "solution_%d.h5" % refinement
2178 file = h5py.File(filename, "r")
2179 mean_values[refinement - 1] = np.array(file["/mean_value"])[0]
2180 point_values[refinement - 1] = np.array(file["/point_value"])[0]
2181 dofs[refinement - 1] = np.array(file["/solution"]).shape[0]
2182@endcode
2183
2184Following is the plot of <code>mean_values</code> on `log-log` scale
2185@code{.py}
2186mean_error = np.abs(mean_values[1:] - mean_values[:-1])
2187plt.loglog(dofs[:-1], mean_error)
2188plt.grid()
2189plt.xlabel("#DoFs")
2190plt.ylabel("mean value error")
2191plt.show()
2192@endcode
2193
2194Following is the plot of <code>point_values</code> on `log-log` scale
2195@code{.py}
2196point_error = np.abs(point_values[1:] - point_values[:-1])
2197plt.loglog(dofs[:-1], point_error)
2198plt.grid()
2199plt.xlabel("#DoFs")
2200plt.ylabel("point value error")
2201plt.show()
2202@endcode
2203
2204A Python package which mimics the `R` ggplot2 (which is based on specifying the grammar of the graphics) is
2205<a href="https://plotnine.org/">plotnine</a>.
2206@code{.py}
2207We need to import the following from the <code>plotnine</code> package
2208from plotnine import (
2209 ggplot,
2210 aes,
2211 geom_raster,
2212 geom_polygon,
2213 geom_line,
2214 labs,
2215 scale_x_log10,
2216 scale_y_log10,
2217 ggtitle,
2218)
2219@endcode
2220Then plot the mesh <code>meshdata</code> dataframe
2221@code{.py}
2222plot = (
2223 ggplot(meshdata, aes(x="x", y="y", group="ids"))
2224 + geom_polygon(fill="white", colour="black")
2225 + ggtitle("grid at refinement level #%d" % refinement)
2226)
2227
2228print(plot)
2229@endcode
2230Collect the solution into a dataframe
2231@code{.py}
2232colordata = pd.DataFrame({"x": x, "y": y, "solution": solution.flatten()})
2233@endcode
2234Plot of the solution
2235@code{.py}
2236plot = (
2237 ggplot(colordata, aes(x="x", y="y", fill="solution"))
2238 + geom_raster(interpolate=True)
2239 + ggtitle("solution at refinement level #%d" % refinement)
2240)
2241
2242print(plot)
2243@endcode
2244
2245Collect the convergence data into a dataframe
2246@code{.py}
2247convdata = pd.DataFrame(
2248 {"dofs": dofs[:-1], "mean_value": mean_error, "point_value": point_error}
2249)
2250
2251@endcode
2252Following is the plot of <code>mean_values</code> on `log-log` scale
2253@code{.py}
2254plot = (
2255 ggplot(convdata, mapping=aes(x="dofs", y="mean_value"))
2256 + geom_line()
2257 + labs(x="#DoFs", y="mean value error")
2258 + scale_x_log10()
2259 + scale_y_log10()
2260)
2261
2262plot.save("mean_error.pdf", dpi=60)
2263print(plot)
2264@endcode
2265
2266Following is the plot of <code>point_values</code> on `log-log` scale
2267@code{.py}
2268plot = (
2269 ggplot(convdata, mapping=aes(x="dofs", y="point_value"))
2270 + geom_line()
2271 + labs(x="#DoFs", y="point value error")
2272 + scale_x_log10()
2273 + scale_y_log10()
2274)
2275
2276plot.save("point_error.pdf", dpi=60)
2277print(plot)
2278@endcode
2279 *
2280 *
2281<a name="step_3-PlainProg"></a>
2282<h1> The plain program</h1>
2283@include "step-3.cc"
2284*/
*  *  for(const auto &cell :triangulation.active_cell_iterators())
*  *  int main(int argc, char **argv)
*  x_component_mask set(0, true)
*  *  *  struct InterferenceTaperTransform *  
*  *  iterator()=default
void distribute_dofs(const FiniteElement< dim, spacedim > &fe)
const ObserverPointer< const Mapping< dim, spacedim >, FEValuesBase< dim, spacedim > > mapping
Definition fe_q.h:552
Definition point.h:111
Point< 2 > second
Definition grid_out.cc:4640
Point< 2 > first
Definition grid_out.cc:4639
unsigned int level
Definition grid_out.cc:4642
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:562
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_gradients
Shape function gradients.
std::vector< index_type > data
Definition mpi.cc:734
const Event initial
Definition event.cc:69
void convert_hypercube_to_simplex_mesh(const Triangulation< dim, spacedim > &in_tria, Triangulation< dim, spacedim > &out_tria, const unsigned int n_divisions=(dim==2 ? 8u :24u))
void reference_cell(Triangulation< dim, spacedim > &tria, const ReferenceCell< dim > &reference_cell)
void hyper_cube(Triangulation< dim, spacedim > &tria, const double left=0., const double right=1., const bool colorize=false)
void simplex(Triangulation< dim, dim > &tria, const std::vector< Point< dim > > &vertices)
constexpr char O
@ matrix
Contents is actually a matrix.
@ general
No special properties.
constexpr types::blas_int zero
constexpr types::blas_int one
void cell_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const FEValuesBase< dim > &fetest, const ArrayView< const std::vector< double > > &velocity, const double factor=1.)
Definition advection.h:72
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
Definition utilities.cc:190
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 > b(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
*  *  *  ScaleZFunction< dim, Number, components >::ScaleZFunction *  component(component)
*  *  *  *  std::vector< Number > ThermoPlasticMaterial< dim, ViscoplasticYieldLaw, Number >::get_state_parameters   const
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 point_value(const DoFHandler< dim, spacedim > &dof, const VectorType &fe_function, const Point< spacedim, double > &point, Vector< typename VectorType::value_type > &value)
bool check(const ConstraintKinds kind_in, const unsigned int dim)
int(&) functions(const void *v1, const void *v2)
void assemble(const MeshWorker::DoFInfoBox< dim, DOFINFO > &dinfo, A *assembler)
Definition loop.h:68
void reinit(MatrixBlock< MatrixType > &v, const BlockSparsityPattern &p)
STL namespace.
::VectorizedArray< Number, width > log(const ::VectorizedArray< Number, width > &)
Definition types.h:30