405 *
double return_value = 0.0;
406 *
for (
unsigned int i = 0; i < dim; ++i)
407 *
return_value += 4.0 *
std::pow(p(i), 4.0);
409 *
return return_value;
423 *
const unsigned int )
const
433 * <a name=
"ImplementationofthecodeStep4codeclass"></a>
445 * will also replace instances of <code>RightHandSide@<dim@></code> by
446 * <code>RightHandSide@<2@></code> and instantiate the latter class from the
451 * In fact, the compiler will also find a declaration <code>Step4@<3@></code>
452 * in <code>main()</code>. This will cause it to again go back to the general
453 * <code>Step4@<dim@></code> template, replace all occurrences of
454 * <code>dim</code>, this time by 3, and compile the class a second time. Note
455 * that the two instantiations <code>Step4@<2@></code> and
456 * <code>Step4@<3@></code> are completely independent classes; their only
457 * common feature is that they are both instantiated from the same general
458 * template, but they are not convertible into each other, for example, and
459 * share no code (both instantiations are compiled completely independently).
467 * <a name="Step4Step4"></a>
468 * <h4>Step4::Step4</h4>
472 * After this introduction, here is the constructor of the <code>Step4</code>
473 * class. It specifies the desired polynomial degree of the finite elements
474 * and associates the DoFHandler to the triangulation just as in the previous
475 * example program, @ref step_3 "step-3":
479 * Step4<dim>::Step4()
481 * , dof_handler(triangulation)
488 * <a name="Step4make_grid"></a>
489 * <h4>Step4::make_grid</h4>
493 * Grid creation is something inherently dimension dependent. However, as long
494 * as the domains are sufficiently similar in 2d or 3d, the library can
495 * abstract for you. In our case, we would like to again solve on the square
496 * @f$[-1,1]\times [-1,1]@f$ in 2d, or on the cube @f$[-1,1] \times [-1,1] \times
497 * [-1,1]@f$ in 3d; both can be termed GridGenerator::hyper_cube(), so we may
498 * use the same function in whatever dimension we are. Of course, the
499 * functions that create a hypercube in two and three dimensions are very much
500 * different, but that is something you need not care about. Let the library
501 * handle the difficult things.
505 * void Step4<dim>::make_grid()
507 * GridGenerator::hyper_cube(triangulation, -1, 1);
508 * triangulation.refine_global(4);
510 * std::cout << " Number of active cells: " << triangulation.n_active_cells()
512 * << " Total number of cells: " << triangulation.n_cells()
519 * <a name="Step4setup_system"></a>
520 * <h4>Step4::setup_system</h4>
524 * This function looks exactly like in the previous example, although it
525 * performs actions that in their details are quite different if
526 * <code>dim</code> happens to be 3. The only significant difference from a
534 *
dof_handler.distribute_dofs(fe);
536 *
std::cout <<
" Number of degrees of freedom: " << dof_handler.n_dofs()
541 *
sparsity_pattern.copy_from(
dsp);
543 *
system_matrix.
reinit(sparsity_pattern);
545 *
solution.
reinit(dof_handler.n_dofs());
553 * <a name=
"Step4assemble_system"></a>
577 * void Step4<dim>::assemble_system()
579 * QGauss<dim> quadrature_formula(fe.degree + 1);
583 * We wanted to have a non-constant right hand side, so we use an object of
584 * the class declared above to generate the necessary data. Since this right
585 * hand side object is only used locally in the present function, we declare
586 * it here as a local variable:
589 * RightHandSide<dim> right_hand_side;
593 * Compared to the previous example, in order to evaluate the non-constant
594 * right hand side function we now also need the quadrature points on the
595 * cell we are presently on (previously, we only required values and
596 * gradients of the shape function from the FEValues object, as well as the
597 * quadrature weights, FEValues::JxW() ). We can tell the FEValues object to
598 * do for us by also giving it the #update_quadrature_points flag:
601 * FEValues<dim> fe_values(fe,
602 * quadrature_formula,
603 * update_values | update_gradients |
604 * update_quadrature_points | update_JxW_values);
608 * We then again define the same abbreviation as in the previous program.
609 * The value of this variable of course depends on the dimension which we
610 * are presently using, but the FiniteElement class does all the necessary
615 *
const unsigned
int dofs_per_cell = fe.n_dofs_per_cell();
620 *
std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
633 *
for (
const auto &cell : dof_handler.active_cell_iterators())
655 *
for (
const unsigned int q_index : fe_values.quadrature_point_indices())
656 *
for (
const unsigned
int i : fe_values.dof_indices())
660 *
(fe_values.shape_grad(i, q_index) *
661 *
fe_values.shape_grad(
j, q_index) *
662 *
fe_values.JxW(q_index));
664 *
const auto &
x_q = fe_values.quadrature_point(q_index);
665 *
cell_rhs(i) += (fe_values.shape_value(i, q_index) *
667 *
fe_values.JxW(q_index));
686 * dimension;
from a
user's perspective, this is not something worth
687 * bothering with, however, making things a lot simpler if one wants to
688 * write code dimension independently.
692 * With the local systems assembled, the transfer into the global matrix
693 * and right hand side is done exactly as before, but here we have again
694 * merged some loops for efficiency:
697 * cell->get_dof_indices(local_dof_indices);
698 * for (const unsigned int i : fe_values.dof_indices())
700 * for (const unsigned int j : fe_values.dof_indices())
701 * system_matrix.add(local_dof_indices[i],
702 * local_dof_indices[j],
703 * cell_matrix(i, j));
705 * system_rhs(local_dof_indices[i]) += cell_rhs(i);
711 * As the final step in this function, we wanted to have non-homogeneous
712 * boundary values in this example, unlike the one before. This is a simple
713 * task, we only have to replace the Functions::ZeroFunction used there by an
714 * object of the class which describes the boundary values we would like to
715 * use (i.e. the <code>BoundaryValues</code> class declared above):
719 * The function VectorTools::interpolate_boundary_values() will only work
720 * on faces that have been marked with boundary indicator 0 (because that's
743 * <a name=
"Step4solve"></a>
744 * <
h4>Step4::solve</
h4>
766 *
std::cout <<
" " << solver_control.last_step()
767 *
<<
" CG iterations needed to obtain convergence." << std::endl;
774 * <a name=
"Step4output_results"></a>
796 *
data_out.attach_dof_handler(dof_handler);
797 *
data_out.add_data_vector(solution,
"solution");
799 *
data_out.build_patches();
801 *
std::ofstream output(dim == 2 ?
"solution-2d.vtk" :
"solution-3d.vtk");
802 *
data_out.write_vtk(output);
810 * <a name=
"Step4run"></a>
811 * <
h4>Step4::run</
h4>
823 *
std::cout <<
"Solving problem in " << dim <<
" space dimensions."
837 * <a name=
"Thecodemaincodefunction"></a>
863 * Each of the two blocks is enclosed in braces to make sure that the
864 * <code>laplace_problem_2d</code> variable goes out of scope (and releases
865 * the memory it holds) before we move on to allocate memory for the 3d
866 * case. Without the additional braces, the <code>laplace_problem_2d</code>
867 * variable would only be destroyed at the end of the function, i.e. after
868 * running the 3d problem, and would needlessly hog memory while the 3d run
869 * could actually use it.
875 * Step4<2> laplace_problem_2d;
876 * laplace_problem_2d.run();
880 * Step4<3> laplace_problem_3d;
881 * laplace_problem_3d.run();
887<a name="Results"></a><h1>Results</h1>
891The output of the program looks as follows (the number of iterations
892may vary by one or two, depending on your computer, since this is
893often dependent on the round-off accuracy of floating point
894operations, which differs between processors):
896Solving problem in 2 space dimensions.
897 Number of active cells: 256
898 Total number of cells: 341
899 Number of degrees of freedom: 289
900 26 CG iterations needed to obtain convergence.
901Solving problem in 3 space dimensions.
902 Number of active cells: 4096
903 Total number of cells: 4681
904 Number of degrees of freedom: 4913
905 30 CG iterations needed to obtain convergence.
907It is obvious that in three spatial dimensions the number of cells and
908therefore also the number of degrees of freedom is
909much higher. What cannot be seen here, is that besides this higher
910number of rows and columns in the matrix, there are also significantly
911more entries per row of the matrix in three space
912dimensions. Together, this leads to a much higher numerical effort for
913solving the system of equation, which you can feel in the run time of the two
914solution steps when you actually run the program.
918The program produces two files: <code>solution-2d.vtk</code> and
919<code>solution-3d.vtk</code>, which can be viewed using the programs
920VisIt or Paraview (in case you do not have these programs, you can easily
922output format in the program to something which you can view more
923easily). Visualizing solutions is a bit of an art, but it can also be fun, so
924you should play around with your favorite visualization tool to get familiar
928 <
img src=
"https://www.dealii.org/images/steps/developer/step-4.solution-2d.png" alt=
"">
931(
See also <a
href=
"http://www.math.colostate.edu/~bangerth/videos.676.11.html">
video lecture 11</a>, <a
href=
"http://www.math.colostate.edu/~bangerth/videos.676.32.html">
video lecture 32</a>.)
1068solution
has been computed (the `output_results()` function seems like a good
1069place to also do postprocessing, for example):
1080 for (
const auto &cell : dof_handler.active_cell_iterators())
1083 fe_values.get_function_values(solution, solution_values);
1085 for (
const unsigned int q_point : fe_values.quadrature_point_indices())
1102This program of course also solves the same Poisson equation in three space
1103dimensions. In this situation, the Poisson equation is often used as a model
1104for diffusion of either a physical species (say, of ink in a tank of water,
1105or a pollutant in the air) or of energy (specifically, of thermal energy in
1106a solid body). In that context, the quantity
1108 \Phi_h = \int_{\partial\Omega} \nabla u_h(\mathbf x) \cdot \mathbf n(\mathbf x) \; dx
1110is the *flux* of this species or energy across the boundary. (In actual
1111physical models, one would also have to multiply the right hand side by
1112a diffusivity or conductivity constant, but let us ignore this here.) In
1113much the same way as before, we compute such integrals by splitting
1114it over integrals of *faces* of cells, and then applying quadrature:
1118 \int_{\partial\Omega} \nabla u_h(\mathbf x) \cdot \mathbf n(\mathbf x) \; dx
1122 \sum_{f \in \text{faces of @f$K@f$}, f\subset\partial\Omega}
1123 \int_f \nabla u_h(\mathbf x) \cdot \mathbf n(\mathbf x) \; dx
1127 \sum_{f \in \text{faces of @f$K@f$}, f\subset\partial\Omega}
1128 \sum_q \nabla u_h(\mathbf x_q^f) \cdot \mathbf n(\mathbf x_q^f) w_q^f,
1130where now @f$\mathbf x_q^f@f$ are the quadrature points located on face @f$f@f$,
1131and @f$w_q^f@f$ are the weights associated with these faces. The second
1132of the sum symbols loops over all faces of cell @f$K@f$, but restricted to
1133those that are actually at the boundary.
1135This all is easily implemented by the following code that replaces the use of the
1136FEValues class (which is used for integrating over cells -- i.e., domain integrals)
1137by the FEFaceValues class (which is used for integrating over faces -- i.e.,
1140 QGauss<dim - 1> face_quadrature_formula(fe.degree + 1);
1141 FEFaceValues<dim> fe_face_values(fe,
1142 face_quadrature_formula,
1143 update_gradients | update_normal_vectors |
1146 std::vector<Tensor<1, dim>> solution_gradients(face_quadrature_formula.size());
1149 for (const auto &cell : dof_handler.active_cell_iterators())
1150 for (const auto &face : cell->face_iterators())
1151 if (face->at_boundary())
1153 fe_face_values.reinit(cell, face);
1154 fe_face_values.get_function_gradients(solution, solution_gradients);
1156 for (const unsigned int q_point :
1157 fe_face_values.quadrature_point_indices())
1159 flux += solution_gradients[q_point] *
1160 fe_face_values.normal_vector(q_point) *
1161 fe_face_values.JxW(q_point);
1164 std::cout << " Flux=" << flux << std::endl;
1167If you add these two code snippets to the code, you will get output like the
1168following when you run the program:
1170Solving problem in 2 space dimensions.
1171 Number of active cells: 256
1172 Total number of cells: 341
1173 Number of degrees of freedom: 289
1174 26 CG iterations needed to obtain convergence.
1175 Mean value of u=1.33303
1177Solving problem in 3 space dimensions.
1178 Number of active cells: 4096
1179 Total number of cells: 4681
1180 Number of degrees of freedom: 4913
1181 30 CG iterations needed to obtain convergence.
1182 Mean value of u=1.58058
1186This makes some sense: If you look, for example, at the 2d output above,
1187the solution varies between values of 1 and 2, but with a larger part of the
1188solution closer to one than two; so an average value of 1.33 for the mean value
1189is reasonable. For the flux, recall that @f$\nabla u \cdot \mathbf n@f$ is the
1190directional derivative in the normal direction -- in other words, how the
1191solution changes as we move from the interior of the domain towards the
1192boundary. If you look at the 2d solution, you will realize that for most parts
1193of the boundary, the solution *decreases* as we approach the boundary, so the
1194normal derivative is negative -- so if we integrate along the boundary, we
1195should expect (and obtain!) a negative value.
1199<a name="extensions"></a>
1200<a name="Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
1203There are many ways with which one can play with this program. The simpler
1204ones include essentially all the possibilities already discussed in the
1205<a href="step_3.html#extensions" target="body">Possibilities for extensions in the documentation of step 3</a>,
1206except that you will have to think about whether something now also applies
1207to the 3d case discussed in the current program.
1209It is also worthwhile considering the postprocessing options discussed
1210above. The documentation states two numbers (the mean value and the
1211normal flux) for both the 2d and 3d cases. Can we trust these
1212numbers? We have convinced ourselves that at least the mean value
1213is reasonable, and that the sign of the flux is probably correct.
1214But are these numbers accurate?
1216A general rule is that we should never trust a number unless we have
1217verified it in some way. From the theory of finite element methods,
1218we know that as we make the mesh finer and finer, the numerical
1219solution @f$u_h@f$ we compute here must converge to the exact solution
1220@f$u@f$. As a consequence, we also expect that @f$\bar u_h \rightarrow \bar u@f$
1221and @f$\Phi_h \rightarrow \Phi@f$, but that does not mean that for any
1222given mesh @f$\bar u_h@f$ or @f$\Phi_h@f$ are particularly accurate approximations.
1224To test this kind of thing, we have already considered the convergence of
1225a point value in @ref step_3 "step-3". We can do the same here by selecting how many
1226times the mesh is globally refined in the `make_grid()` function of this
1227program. For the mean value of the solution, we then get the following
1229 <table align="center" class="doxtable">
1230 <tr> <th># of refinements</th>
1231 <th>@f$\bar u_h@f$ in 2d</th>
1232 <th>@f$\bar u_h@f$ in 3d</th>
1234 <tr> <td>4</td> <td>1.33303</td> <td>1.58058</td> </tr>
1235 <tr> <td>5</td> <td>1.33276</td> <td>1.57947</td> </tr>
1236 <tr> <td>6</td> <td>1.3327</td> <td>1.5792</td> </tr>
1237 <tr> <td>7</td> <td>1.33269</td> <td>1.57914</td> </tr>
1238 <tr> <td>8</td> <td>1.33268</td> <td></td> </tr>
1239 <tr> <td>9</td> <td>1.33268</td> <td></td> </tr>
1241I did not have the patience to run the last two values for the 3d case --
1242one needs quite a fine mesh for this, with correspondingly long run times.
1243But we can be reasonably assured that values around 1.33 (for the 2d case)
1244and 1.58 (for the 3d case) are about right -- and at least for engineering
1245applications, three digits of accuracy are good enough.
1247The situation looks very different for the flux. Here, we get results
1248such as the following:
1249 <table align="center" class="doxtable">
1250 <tr> <th># of refinements</th>
1251 <th>@f$\Phi_h@f$ in 2d</th>
1252 <th>@f$\Phi_h@f$ in 3d</th>
1254 <tr> <td>4</td> <td>-3.68956</td> <td>-8.29435</td> </tr>
1255 <tr> <td>5</td> <td>-4.90147</td> <td>-13.0691</td> </tr>
1256 <tr> <td>6</td> <td>-5.60745</td> <td>-15.9171</td> </tr>
1257 <tr> <td>7</td> <td>-5.99111</td> <td>-17.4918</td> </tr>
1258 <tr> <td>8</td> <td>-6.19196</td> <td></td> </tr>
1259 <tr> <td>9</td> <td>-6.29497</td> <td></td> </tr>
1260 <tr> <td>10</td> <td>-6.34721</td> <td></td> </tr>
1261 <tr> <td>11</td> <td>-6.37353</td> <td></td> </tr>
1263So this is not great. For the 2d case, we might infer that perhaps
1264a value around -6.4 might be right if we just refine the mesh enough --
1265though 11 refinements already leads to some 4,194,304 cells. In any
1266case, the first number (the one shown in the beginning where we
1267discussed postprocessing) was off by almost a factor of 2!
1269For the 3d case, the last number shown was on a mesh with 2,097,152
1270cells; the next one would have had 8 times as many cells. In any case, the
1275good job convincing us that the code might be correctly implemented.
1277If you keep reading through the other tutorial programs, you will find many ways
1278to make these sorts of computations more accurate and to come to
1279believe that the flux actually does converge to its correct value.
1280For example, we can dramatically increase the accuracy of the computation
1281by using adaptive mesh refinement (@ref step_6 "step-6") near the boundary, and
1282in particular by using higher polynomial degree finite elements (also
1283@ref step_6 "step-6", but also @ref step_7 "step-7"). Using the latter, using cubic elements
1284(polynomial degree 3), we can actually compute the flux pretty
1285accurately even in 3d: @f$\Phi_h=-19.0148@f$ with 4 global refinement steps,
1286and @f$\Phi_h=-19.1533@f$ with 5 refinement steps. These numbers are already
1287pretty close together and give us a reasonable idea of the first
1288two correct digits of the "true" answer.
1290@note We would be remiss to not also comment on the fact that there
1291 are good theoretical reasons why computing the flux accurately
1292 appears to be so much more difficult than the average value.
1293 This has to do with the fact that finite element theory
1294 provides us with the estimate
1295 @f$\|u-u_h\|_{L_2(\Omega)} \le C h^2 \|\nabla^2u\|_{L_2(\Omega)}@f$
1296 when using the linear elements this program uses -- that is, for
1297 every global mesh refinement, @f$h@f$ is reduced by a factor of two
1298 and the error goes down by a factor of 4. Now, the @f$L_2@f$ error is
1299 not equivalent to the error in the mean value, but the two are
1300 related: They are both integrals over the domain, using the *value*
1301 of the solution. We expect the mean value to converge no worse than
1302 the @f$L_2@f$ norm of the error. At the same time, theory also provides
1303 us with this estimate:
1304 @f$\|\nabla (u-u_h)\|_{L_2(\partial\Omega)} \le
1305 C h^{1/2} \|\nabla^2u\|_{L_2(\Omega)}@f$. The move from values to
1306 gradients reduces the convergence rates by one order, and the move
1307 from domain to boundary by another half order. Here, then, each
1308 refinement step reduces the error not by a factor of 4 any more,
1309 by only by a factor of @f$\sqrt{2} \approx 1.4@f$. It takes a lot
1310 of global refinement steps to reduce the error by, say, a factor
1311 ten or hundred, and this is reflected in the very slow convergence
1312 evidenced by the table. On the other hand, for cubic elements (i.e.,
1313 polynomial degree 3), we would get
1314 @f$\|u-u_h\|_{L_2(\Omega)} \le C h^4 \|\nabla^4u\|_{L_2(\Omega)}@f$
1315 and after reduction by 1.5 orders, we would still have
1316 @f$\|\nabla (u-u_h)\|_{L_2(\partial\Omega)} \le
1317 C h^{2+1/2} \|\nabla^4u\|_{L_2(\Omega)}@f$. This rate,
1318 @f${\cal O}(h^{2.5})@f$ is still quite rapid, and it is perhaps not
1319 surprising that we get much better answers with these higher
1320 order elements. This also illustrates that when trying to
1321 approximate anything that relates to a gradient of the solution,
1322 using linear elements (polynomial degree one) is really not a
1325@note In this very specific case, it turns out that we can actually
1326 compute the exact value of @f$\Phi@f$. This is because for the Poisson
1327 equation we compute the solution of here, @f$-\Delta u = f@f$, we can
1328 integrate over the domain, @f$-\int_\Omega \Delta u = \int_\Omega f@f$,
1329 and then use that @f$\Delta = \text{div}\;\text{grad}@f$; this allows
1331 divergence theorem followed by multiplying by minus one to find
1332 @f$\int_{\partial\Omega} \nabla u \cdot n = -\int_\Omega f@f$. The
1333 left hand side happens to be @f$\Phi@f$. For the specific right
1334 hand side @f$f(x_1,x_2)=4(x_1^4+x_2^4)@f$ we use in 2d, we then
1335 get @f$-\int_\Omega f = -\int_{-1}^{1} \int_{-1}^{1} 4(x_1^4+x_2^4) \; dx_2\; dx_1
1336 = -16 \left[\int_{-1}^{1} x^4 \; dx\right] = -16\times\frac 25@f$,
1337 which has a numerical value of exactly -6.4 -- right on with our
1338 guess above. In 3d, we can do the same and get that the exact
1341 -\int_{-1}^{1} \int_{-1}^{1} \int_{-1}^{1} 4(x_1^4+x_2^4+x_3^4) \; dx_3 \; dx_2\; dx_1
1342 = -48\times\frac 25=-19.2@f$. What we found with cubic elements
1343 is then quite close to this exact value. Of course, in practice
1344 we almost never have exact values to compare with: If we could
1345 compute something on a piece of paper, we wouldn't
have to solve
1354<a name=
"PlainProg"></a>
value_type * data() const noexcept
void reinit(value_type *starting_element, const std::size_t n_elements)
void loop(ITERATOR begin, std_cxx20::type_identity_t< ITERATOR > end, DOFINFO &dinfo, INFOBOX &info, const std::function< void(DOFINFO &, typename INFOBOX::CellInfo &)> &cell_worker, const std::function< void(DOFINFO &, typename INFOBOX::CellInfo &)> &boundary_worker, const std::function< void(DOFINFO &, DOFINFO &, typename INFOBOX::CellInfo &, typename INFOBOX::CellInfo &)> &face_worker, ASSEMBLER &assembler, const LoopControl &lctrl=LoopControl())
void make_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern, const AffineConstraints< number > &constraints=AffineConstraints< number >(), const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt K
@ matrix
Contents is actually a matrix.
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.)
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
T sum(const T &t, const MPI_Comm mpi_communicator)
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)
DEAL_II_HOST constexpr TableIndices< 2 > merge(const TableIndices< 2 > &previous_indices, const unsigned int new_index, const unsigned int position)
int(&) functions(const void *v1, const void *v2)
void assemble(const MeshWorker::DoFInfoBox< dim, DOFINFO > &dinfo, A *assembler)
::VectorizedArray< Number, width > pow(const ::VectorizedArray< Number, width > &, const Number p)