1070 * sparsity_pattern.copy_from(dsp);
1072 * system_matrix.reinit(sparsity_pattern);
1074 * solution.reinit(dof_handler.n_dofs());
1075 * system_rhs.reinit(dof_handler.n_dofs());
1082 * In the following function, the
matrix and right hand side are
1083 * assembled. As stated in the documentation of the main
class above, it
1084 * does not
do this itself, but rather delegates to the function following
1085 * next, utilizing the
WorkStream concept discussed in @ref threads .
1089 * If you have looked through the @ref threads topic, you will have
1090 * seen that assembling in
parallel does not take an incredible
1091 * amount of extra code as long as you diligently describe what the
1092 * scratch and
copy data objects are, and if you define suitable
1093 *
functions for the local assembly and the
copy operation from local
1094 * contributions to global objects. This done, the following will do
1095 * all the heavy lifting to get these operations done on multiple
1096 * threads on as many cores as you have in your system:
1099 * template <
int dim>
1100 * void AdvectionProblem<dim>::assemble_system()
1103 * dof_handler.
end(),
1105 * &AdvectionProblem::local_assemble_system,
1106 * &AdvectionProblem::copy_local_to_global,
1107 * AssemblyScratchData(fe),
1108 * AssemblyCopyData());
1115 * As already mentioned above, we need to have scratch objects for
1116 * the
parallel computation of local contributions. These objects
1118 * we will need to have constructors and
copy constructors that allow us to
1119 * create them. For the cell terms we need the
values
1121 * order to determine the source density and the advection field at
1122 * a given
point, and the weights of the quadrature points times the
1123 *
determinant of the Jacobian at these points. In contrast, for the
1124 * boundary integrals, we don't need the
gradients, but rather the
1125 * normal vectors to the cells. This determines which update flags
1126 * we will have to pass to the constructors of the members of the
1130 * template <
int dim>
1131 * AdvectionProblem<dim>::AssemblyScratchData::AssemblyScratchData(
1134 *
QGauss<dim>(fe.degree + 1),
1137 * , fe_face_values(fe,
1138 *
QGauss<dim - 1>(fe.degree + 1),
1141 * , rhs_values(fe_values.get_quadrature().
size())
1142 * , advection_directions(fe_values.get_quadrature().
size())
1143 * , face_boundary_values(fe_face_values.get_quadrature().
size())
1144 * , face_advection_directions(fe_face_values.get_quadrature().
size())
1149 * template <
int dim>
1150 * AdvectionProblem<dim>::AssemblyScratchData::AssemblyScratchData(
1151 * const AssemblyScratchData &scratch_data)
1152 * : fe_values(scratch_data.fe_values.get_fe(),
1153 * scratch_data.fe_values.get_quadrature(),
1156 * , fe_face_values(scratch_data.fe_face_values.get_fe(),
1157 * scratch_data.fe_face_values.get_quadrature(),
1160 * , rhs_values(scratch_data.rhs_values.
size())
1161 * , advection_directions(scratch_data.advection_directions.
size())
1162 * , face_boundary_values(scratch_data.face_boundary_values.
size())
1163 * , face_advection_directions(scratch_data.face_advection_directions.
size())
1170 * Now, this is the function that does the actual work. It is not very
1171 * different from the <code>assemble_system</code>
functions of previous
1172 * example programs, so we will again only comment on the differences. The
1173 * mathematical stuff closely follows what we have said in the introduction.
1177 * There are a number of points worth mentioning here, though. The
1179 * objects into the ScratchData object. We have done so because the
1180 * alternative would have been to simply create one every time we
1181 * get into this function -- i.
e., on every cell. It now turns out
1182 * that the
FEValues classes were written with the explicit goal of
1183 * moving everything that remains the same from cell to cell into
1184 * the construction of the object, and only do as little work as
1186 * cell. What this means is that it would be very expensive to
1187 * create a new object of this kind in this function as we would
1188 * have to do it for every cell -- exactly the thing we wanted to
1189 * avoid with the
FEValues class. Instead, what we do is create it
1190 * only once (or a small number of times) in the scratch objects and
1191 * then re-use it as often as we can.
1195 * This begs the question of whether there are other objects we
1196 * create in this function whose creation is expensive compared to
1197 * its use. Indeed, at the top of the function, we declare all sorts
1198 * of objects. The <code>AdvectionField</code>,
1199 * <code>RightHandSide</code> and <code>BoundaryValues</code> do not
1200 * cost much to create, so there is no harm here. However,
1201 * allocating memory in creating the <code>rhs_values</code> and
1202 * similar variables below typically costs a significant amount of
1203 * time, compared to just accessing the (temporary)
values we store
1204 * in them. Consequently, these would be candidates for moving into
1205 * the <code>AssemblyScratchData</code> class. We will leave this as
1209 * template <
int dim>
1210 * void AdvectionProblem<dim>::local_assemble_system(
1211 * const typename
DoFHandler<dim>::active_cell_iterator &cell,
1212 * AssemblyScratchData &scratch_data,
1213 * AssemblyCopyData ©_data)
1217 * We define some abbreviations to avoid unnecessarily long lines:
1220 * const unsigned
int dofs_per_cell = fe.n_dofs_per_cell();
1221 *
const unsigned int n_q_points =
1222 * scratch_data.fe_values.get_quadrature().size();
1223 *
const unsigned int n_face_q_points =
1224 * scratch_data.fe_face_values.get_quadrature().size();
1228 * We declare cell
matrix and cell right hand side...
1231 * copy_data.cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
1232 * copy_data.cell_rhs.reinit(dofs_per_cell);
1236 * ... an array to hold the global indices of the degrees of freedom of
1237 * the cell on which we are presently working...
1240 * copy_data.local_dof_indices.resize(dofs_per_cell);
1244 * ... then initialize the <code>
FEValues</code>
object...
1247 * scratch_data.fe_values.
reinit(cell);
1251 * ... obtain the
values of right hand side and advection directions
1252 * at the quadrature points...
1255 * scratch_data.advection_field.value_list(
1256 * scratch_data.fe_values.get_quadrature_points(),
1257 * scratch_data.advection_directions);
1258 * scratch_data.right_hand_side.value_list(
1259 * scratch_data.fe_values.get_quadrature_points(), scratch_data.rhs_values);
1263 * ... set the
value of the streamline diffusion parameter as
1264 * described in the introduction...
1267 *
const double delta = 0.1 * cell->diameter();
1271 * ... and
assemble the local contributions to the system
matrix and
1272 * right hand side as also discussed above:
1275 *
for (
unsigned int q_point = 0; q_point < n_q_points; ++q_point)
1276 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1280 * Alias the AssemblyScratchData
object to keep the lines from
1284 *
const auto &sd = scratch_data;
1285 *
for (
unsigned int j = 0; j < dofs_per_cell; ++j)
1286 * copy_data.cell_matrix(i, j) +=
1287 * ((sd.fe_values.shape_value(i, q_point) +
1288 * delta * (sd.advection_directions[q_point] *
1289 * sd.fe_values.shape_grad(i, q_point))) *
1290 * sd.advection_directions[q_point] *
1291 * sd.fe_values.shape_grad(j, q_point)) *
1292 * sd.fe_values.JxW(q_point);
1294 * copy_data.cell_rhs(i) +=
1295 * (sd.fe_values.shape_value(i, q_point) +
1296 * delta * (sd.advection_directions[q_point] *
1297 * sd.fe_values.shape_grad(i, q_point))) *
1298 * sd.rhs_values[q_point] *
1299 * sd.fe_values.JxW(q_point);
1304 * Besides the cell terms which we have built up now, the bilinear
1305 * form of the present problem also contains terms on the boundary of
1306 * the domain. Therefore, we have to
check whether any of the faces of
1307 *
this cell are on the boundary of the domain, and
if so
assemble the
1308 * contributions of
this face as well. Of course, the bilinear form
1309 * only contains contributions from the <code>inflow</code> part of
1310 * the boundary, but to find out whether a certain part of a face of
1311 * the present cell is part of the inflow boundary, we have to have
1312 * information on the exact location of the quadrature points and on
1313 * the direction of flow at
this point; we obtain
this information
1315 * whether a quadrature
point is on the inflow boundary.
1318 *
for (
const auto &face : cell->face_iterators())
1319 * if (face->at_boundary())
1323 * Ok, this face of the present cell is on the boundary of the
1324 * domain. Just as for the usual
FEValues object which we have
1325 * used in previous examples and also above, we have to
1326 * reinitialize the
FEFaceValues object for the present face:
1329 * scratch_data.fe_face_values.
reinit(cell, face);
1333 * For the quadrature points at hand, we ask
for the
values of
1334 * the inflow function and
for the direction of flow:
1337 * scratch_data.boundary_values.value_list(
1338 * scratch_data.fe_face_values.get_quadrature_points(),
1339 * scratch_data.face_boundary_values);
1340 * scratch_data.advection_field.value_list(
1341 * scratch_data.fe_face_values.get_quadrature_points(),
1342 * scratch_data.face_advection_directions);
1346 * Now
loop over all quadrature points and see whether
this face is on
1347 * the inflow or outflow part of the boundary. The normal
1348 * vector points out of the cell: since the face is at
1349 * the boundary, the normal vector points out of the domain,
1350 * so
if the advection direction points into the domain, its
1351 *
scalar product with the normal vector must be
negative (to see why
1352 *
this is
true, consider the scalar product definition that uses a
1356 * for (unsigned
int q_point = 0; q_point < n_face_q_points; ++q_point)
1357 *
if (scratch_data.fe_face_values.normal_vector(q_point) *
1358 * scratch_data.face_advection_directions[q_point] <
1362 * If the face is part of the inflow boundary, then compute the
1363 * contributions of
this face to the global matrix and right
1364 * hand side,
using the values obtained from the
1365 *
FEFaceValues object and the formulae discussed in the
1369 * for (unsigned
int i = 0; i < dofs_per_cell; ++i)
1371 *
for (
unsigned int j = 0; j < dofs_per_cell; ++j)
1372 * copy_data.cell_matrix(i, j) -=
1373 * (scratch_data.face_advection_directions[q_point] *
1374 * scratch_data.fe_face_values.normal_vector(q_point) *
1375 * scratch_data.fe_face_values.shape_value(i, q_point) *
1376 * scratch_data.fe_face_values.shape_value(j, q_point) *
1377 * scratch_data.fe_face_values.JxW(q_point));
1379 * copy_data.cell_rhs(i) -=
1380 * (scratch_data.face_advection_directions[q_point] *
1381 * scratch_data.fe_face_values.normal_vector(q_point) *
1382 * scratch_data.face_boundary_values[q_point] *
1383 * scratch_data.fe_face_values.shape_value(i, q_point) *
1384 * scratch_data.fe_face_values.JxW(q_point));
1390 * The
final piece of information the
copy routine needs is the global
1391 * indices of the degrees of freedom on
this cell, so we
end by writing
1392 * them to the local array:
1395 * cell->get_dof_indices(copy_data.local_dof_indices);
1402 * The
second function we needed to write was the one that copies
1403 * the local contributions the previous function computed (and
1404 * put into the AssemblyCopyData
object) into the global
matrix and right
1405 * hand side vector objects. This is essentially what we
always had
1406 * as the last block of code when assembling something on every
1407 * cell. The following should therefore be pretty obvious:
1410 *
template <
int dim>
1412 * AdvectionProblem<dim>::copy_local_to_global(
const AssemblyCopyData ©_data)
1414 * hanging_node_constraints.distribute_local_to_global(
1415 * copy_data.cell_matrix,
1416 * copy_data.cell_rhs,
1417 * copy_data.local_dof_indices,
1424 * The next function is the linear solver routine. As the system is no longer
1426 * use the Conjugate Gradient method any more. Rather, we use a solver that
1427 * is more
general and does not rely on any special properties of the
1428 *
matrix: the GMRES method. GMRES, like the conjugate
gradient method,
1429 *
requires a decent preconditioner: we use a Jacobi preconditioner here,
1430 * which works well enough
for this problem.
1433 *
template <
int dim>
1434 *
void AdvectionProblem<dim>::solve()
1437 * system_rhs.size() / 10),
1438 * 1e-10 * system_rhs.l2_norm());
1441 * preconditioner.
initialize(system_matrix, 1.0);
1442 * solver.solve(system_matrix, solution, system_rhs, preconditioner);
1444 * hanging_node_constraints.distribute(solution);
1446 * std::cout <<
" Iterations required for convergence: "
1447 * << solver_control.last_step() <<
'\n'
1448 * <<
" Norm of residual at convergence: "
1449 * << solver_control.last_value() <<
'\n';
1454 * The following function refines the grid according to the quantity
1455 * described in the introduction. The respective computations are made in
1456 * the class <code>GradientEstimation</code>.
1459 *
template <
int dim>
1460 *
void AdvectionProblem<dim>::refine_grid()
1464 * GradientEstimation::estimate(dof_handler,
1466 * estimated_error_per_cell);
1469 * estimated_error_per_cell,
1478 * This function is similar to the one in @ref step_6
"step-6", but since we use a higher
1479 * degree finite element we save the solution in a different
1480 * way. Visualization programs like VisIt and Paraview typically only
1481 * understand
data that is associated with nodes: they cannot plot
1482 * fifth-degree basis
functions, which results in a very inaccurate picture
1483 * of the solution we computed. To get around
this we save multiple
1484 * <em>patches</em> per cell: in 2
d we save @f$8\times 8=64@f$ bilinear
1485 * `sub-cells
' to the VTU file for each cell, and in 3d we save
1486 * @f$8\times 8\times 8 = 512@f$. The end result is that the
1487 * visualization program will use a piecewise linear interpolation of the
1488 * cubic basis functions on a 3 times refined mesh:
1489 * This captures the solution detail and, with most
1490 * screen resolutions, looks smooth. We save the grid in a separate step
1491 * with no extra patches so that we have a visual representation of the cell
1496 * @note Version 9.1 of deal.II gained the ability to write higher degree
1497 * polynomials (i.e., write piecewise bicubic visualization data for our
1498 * piecewise bicubic solution) VTK and VTU output: however, not all recent
1499 * versions of ParaView and VisIt (as of 2018) can read this format, so we
1500 * use the older, more general (but less efficient) approach here.
1503 * template <int dim>
1504 * void AdvectionProblem<dim>::output_results(const unsigned int cycle) const
1508 * const std::string filename = "grid-" + std::to_string(cycle) + ".vtu";
1509 * std::ofstream output(filename);
1510 * grid_out.write_vtu(triangulation, output);
1511 * std::cout << " Grid written to " << filename << std::endl;
1515 * DataOut<dim> data_out;
1516 * data_out.attach_dof_handler(dof_handler);
1517 * data_out.add_data_vector(solution, "solution");
1518 * data_out.build_patches(8);
1522 * VTU output can be expensive, both to compute and to write to
1523 * disk. Here we ask ZLib, a compression library, to compress the data
1524 * in a way that maximizes throughput.
1527 * DataOutBase::VtkFlags vtk_flags;
1528 * vtk_flags.compression_level = DataOutBase::CompressionLevel::best_speed;
1529 * data_out.set_flags(vtk_flags);
1531 * const std::string filename = "solution-" + std::to_string(cycle) + ".vtu";
1532 * std::ofstream output(filename);
1533 * data_out.write_vtu(output);
1534 * std::cout << " Solution written to " << filename << std::endl;
1541 * ... as is the main loop (setup -- solve -- refine), aside from the number
1542 * of cycles and the initial grid:
1545 * template <int dim>
1546 * void AdvectionProblem<dim>::run()
1548 * for (unsigned int cycle = 0; cycle < 10; ++cycle)
1550 * std::cout << "Cycle " << cycle << ':
' << std::endl;
1554 * GridGenerator::hyper_cube(triangulation, -1, 1);
1555 * triangulation.refine_global(3);
1563 * std::cout << " Number of active cells: "
1564 * << triangulation.n_active_cells() << std::endl;
1568 * std::cout << " Number of degrees of freedom: "
1569 * << dof_handler.n_dofs() << std::endl;
1571 * assemble_system();
1573 * output_results(cycle);
1582 * <a name="step_9-GradientEstimationclassimplementation"></a>
1583 * <h3>GradientEstimation class implementation</h3>
1587 * Now for the implementation of the <code>GradientEstimation</code> class.
1588 * Let us start by defining constructors for the
1589 * <code>EstimateScratchData</code> class used by the
1590 * <code>estimate_cell()</code> function:
1593 * template <int dim>
1594 * GradientEstimation::EstimateScratchData<dim>::EstimateScratchData(
1595 * const FiniteElement<dim> &fe,
1596 * const Vector<double> &solution,
1597 * Vector<float> &error_per_cell)
1598 * : fe_midpoint_value(fe,
1600 * update_values | update_quadrature_points)
1601 * , solution(solution)
1602 * , error_per_cell(error_per_cell)
1603 * , cell_midpoint_value(1)
1604 * , neighbor_midpoint_value(1)
1608 * We allocate a vector to hold iterators to all active neighbors of
1609 * a cell. We reserve the maximal number of active neighbors in order to
1610 * avoid later reallocations. Note how this maximal number of active
1611 * neighbors is computed here.
1614 * active_neighbors.reserve(GeometryInfo<dim>::faces_per_cell *
1615 * GeometryInfo<dim>::max_children_per_face);
1619 * template <int dim>
1620 * GradientEstimation::EstimateScratchData<dim>::EstimateScratchData(
1621 * const EstimateScratchData &scratch_data)
1622 * : fe_midpoint_value(scratch_data.fe_midpoint_value.get_fe(),
1623 * scratch_data.fe_midpoint_value.get_quadrature(),
1624 * update_values | update_quadrature_points)
1625 * , solution(scratch_data.solution)
1626 * , error_per_cell(scratch_data.error_per_cell)
1627 * , cell_midpoint_value(1)
1628 * , neighbor_midpoint_value(1)
1634 * Next comes the implementation of the <code>GradientEstimation</code>
1635 * class. The first function does not much except for delegating work to the
1636 * other function, but there is a bit of setup at the top.
1640 * Before starting with the work, we check that the vector into
1641 * which the results are written has the right size, using the `Assert`
1642 * macro and the exception class we declared above. Programming
1643 * mistakes in which one forgets to size arguments correctly at the
1644 * calling site are quite common. Because the resulting damage from
1645 * not catching such errors is often subtle (e.g., corruption of
1646 * data somewhere in memory, or non-reproducible results), it is
1647 * well worth the effort to check for such things.
1650 * template <int dim>
1651 * void GradientEstimation::estimate(const DoFHandler<dim> &dof_handler,
1652 * const Vector<double> &solution,
1653 * Vector<float> &error_per_cell)
1656 * error_per_cell.size() == dof_handler.get_triangulation().n_active_cells(),
1657 * ExcInvalidVectorLength(error_per_cell.size(),
1658 * dof_handler.get_triangulation().n_active_cells()));
1660 * WorkStream::run(dof_handler.begin_active(),
1661 * dof_handler.end(),
1662 * &GradientEstimation::template estimate_cell<dim>,
1663 * std::function<void(const EstimateCopyData &)>(),
1664 * EstimateScratchData<dim>(dof_handler.get_fe(),
1667 * EstimateCopyData());
1673 * Here comes the function that estimates the local error by computing the
1674 * finite difference approximation of the gradient. The function first
1675 * computes the list of active neighbors of the present cell and then
1676 * computes the quantities described in the introduction for each of
1677 * the neighbors. The reason for this order is that it is not a one-liner
1678 * to find a given neighbor with locally refined meshes. In principle, an
1679 * optimized implementation would find neighbors and the quantities
1680 * depending on them in one step, rather than first building a list of
1681 * neighbors and in a second step their contributions but we will gladly
1682 * leave this as an exercise. As discussed before, the worker function
1683 * passed to WorkStream::run works on "scratch" objects that keep all
1684 * temporary objects. This way, we do not need to create and initialize
1685 * objects that are expensive to initialize within the function that does
1686 * the work every time it is called for a given cell. Such an argument is
1687 * passed as the second argument. The third argument would be a "copy-data"
1688 * object (see @ref threads for more information) but we do not actually use
1689 * any of these here. Since WorkStream::run() insists on passing three
1690 * arguments, we declare this function with three arguments, but simply
1691 * ignore the last one.
1695 * (This is unsatisfactory from an aesthetic perspective. It can be avoided
1696 * by using an anonymous (lambda) function. If you allow, let us here show
1697 * how. First, assume that we had declared this function to only take two
1698 * arguments by omitting the unused last one. Now, WorkStream::run still
1699 * wants to call this function with three arguments, so we need to find a
1700 * way to "forget" the third argument in the call. Simply passing
1701 * WorkStream::run the pointer to the function as we do above will not do
1702 * this -- the compiler will complain that a function declared to have two
1703 * arguments is called with three arguments. However, we can do this by
1704 * passing the following as the third argument to WorkStream::run():
1705 * <div class=CodeFragmentInTutorialComment>
1707 * [](const typename DoFHandler<dim>::active_cell_iterator &cell,
1708 * EstimateScratchData<dim> & scratch_data,
1709 * EstimateCopyData &)
1711 * GradientEstimation::estimate_cell<dim>(cell, scratch_data);
1715 * This is not much better than the solution implemented below: either the
1716 * routine itself must take three arguments or it must be wrapped by
1717 * something that takes three arguments. We don't use
this since adding the
1718 * unused argument at the beginning is simpler.
1722 * Now
for the details:
1725 *
template <
int dim>
1726 *
void GradientEstimation::estimate_cell(
1728 * EstimateScratchData<dim> &scratch_data,
1729 *
const EstimateCopyData &)
1733 * We need space
for the tensor <code>Y</code>, which is the
sum of
1734 * outer products of the y-vectors.
1741 * First initialize the <code>
FEValues</code> object, as well as the
1742 * <code>Y</code> tensor:
1745 * scratch_data.fe_midpoint_value.
reinit(cell);
1749 * Now, before we go on, we
first compute a list of all active neighbors
1750 * of the present cell. We
do so by
first looping over all faces and see
1751 * whether the neighbor there is active, which would be the
case if it
1752 * is on the same
level as the present cell or one
level coarser (note
1753 * that a neighbor can only be once coarser than the present cell, as
1754 * we only allow a maximal difference of one refinement over a face in
1755 * deal.II). Alternatively, the neighbor could be on the same
level
1756 * and be further refined; then we have to find which of its children
1757 * are next to the present cell and select these (note that
if a child
1758 * of a neighbor of an active cell that is next to
this active cell,
1759 * needs necessarily be active itself, due to the one-refinement rule
1764 * Things are slightly different in one space dimension, as there the
1765 * one-refinement rule does not exist: neighboring active cells may
1766 * differ in as many refinement levels as they like. In
this case, the
1767 * computation becomes a little more difficult, but we will explain
1772 * Before starting the
loop over all neighbors of the present cell, we
1773 * have to clear the array storing the iterators to the active
1774 * neighbors, of course.
1777 * scratch_data.active_neighbors.clear();
1778 *
for (
const auto face_n : cell->face_indices())
1779 * if (!cell->at_boundary(face_n))
1783 * First define an abbreviation for the iterator to the face and
1787 * const auto face = cell->face(face_n);
1788 *
const auto neighbor = cell->neighbor(face_n);
1792 * Then
check whether the neighbor is active. If it is, then it
1793 * is on the same
level or one
level coarser (
if we are not in
1794 * 1d), and we are interested in it in any
case.
1797 *
if (neighbor->is_active())
1798 * scratch_data.active_neighbors.push_back(neighbor);
1803 * If the neighbor is not active, then
check its children.
1810 * To find the child of the neighbor which bounds to the
1811 * present cell, successively go to its right child
if
1812 * we are left of the present cell (n==0), or go to the
1813 * left child
if we are on the right (n==1), until we
1814 * find an active cell.
1817 *
auto neighbor_child = neighbor;
1818 *
while (neighbor_child->has_children())
1819 * neighbor_child = neighbor_child->child(face_n == 0 ? 1 : 0);
1823 * As
this used some non-trivial geometrical intuition,
1824 * we might want to
check whether we did it right,
1825 * i.e.,
check whether the neighbor of the cell we found
1826 * is indeed the cell we are presently working
1827 * on. Checks like
this are often useful and have
1828 * frequently uncovered errors both in algorithms like
1829 * the line above (where it is simple to involuntarily
1830 * exchange <code>n==1</code> for <code>n==0</code> or
1831 * the like) and in the library (the assumptions
1832 * underlying the algorithm above could either be wrong,
1833 * wrongly documented, or are violated due to an error
1834 * in the library). One could in principle remove such
1835 * checks after the program works
for some time, but it
1836 * might be a good things to leave it in anyway to
check
1837 *
for changes in the library or in the algorithm above.
1841 * Note that
if this check fails, then
this is certainly
1842 * an error that is irrecoverable and probably qualifies
1843 * as an
internal error. We therefore use a predefined
1844 * exception
class to throw here.
1847 *
Assert(neighbor_child->neighbor(face_n == 0 ? 1 : 0) == cell,
1848 * ExcInternalError());
1852 * If the
check succeeded, we push the active neighbor
1853 * we just found to the stack we keep:
1856 * scratch_data.active_neighbors.push_back(neighbor_child);
1861 * If we are not in 1
d, we collect all neighbor children
1862 * `behind
' the subfaces of the current face and move on:
1865 * for (unsigned int subface_n = 0; subface_n < face->n_children();
1867 * scratch_data.active_neighbors.push_back(
1868 * cell->neighbor_child_on_subface(face_n, subface_n));
1874 * OK, now that we have all the neighbors, lets start the computation
1875 * on each of them. First we do some preliminaries: find out about the
1876 * center of the present cell and the solution at this point. The
1877 * latter is obtained as a vector of function values at the quadrature
1878 * points, of which there are only one, of course. Likewise, the
1879 * position of the center is the position of the first (and only)
1880 * quadrature point in real space.
1883 * const Point<dim> this_center =
1884 * scratch_data.fe_midpoint_value.quadrature_point(0);
1886 * scratch_data.fe_midpoint_value.get_function_values(
1887 * scratch_data.solution, scratch_data.cell_midpoint_value);
1891 * Now loop over all active neighbors and collect the data we
1895 * Tensor<1, dim> projected_gradient;
1896 * for (const auto &neighbor : scratch_data.active_neighbors)
1900 * Then get the center of the neighbor cell and the value of the
1901 * finite element function at that point. Note that for this
1902 * information we have to reinitialize the <code>FEValues</code>
1903 * object for the neighbor cell.
1906 * scratch_data.fe_midpoint_value.reinit(neighbor);
1907 * const Point<dim> neighbor_center =
1908 * scratch_data.fe_midpoint_value.quadrature_point(0);
1910 * scratch_data.fe_midpoint_value.get_function_values(
1911 * scratch_data.solution, scratch_data.neighbor_midpoint_value);
1915 * Compute the vector <code>y</code> connecting the centers of the
1916 * two cells. Note that as opposed to the introduction, we denote
1917 * by <code>y</code> the normalized difference vector, as this is
1918 * the quantity used everywhere in the computations.
1921 * Tensor<1, dim> y = neighbor_center - this_center;
1922 * const double distance = y.norm();
1927 * Then add up the contribution of this cell to the Y matrix...
1930 * for (unsigned int i = 0; i < dim; ++i)
1931 * for (unsigned int j = 0; j < dim; ++j)
1932 * Y[i][j] += y[i] * y[j];
1936 * ... and update the sum of difference quotients:
1939 * projected_gradient += (scratch_data.neighbor_midpoint_value[0] -
1940 * scratch_data.cell_midpoint_value[0]) /
1946 * If now, after collecting all the information from the neighbors, we
1947 * can determine an approximation of the gradient for the present
1948 * cell, then we need to have passed over vectors <code>y</code> which
1949 * span the whole space, otherwise we would not have all components of
1950 * the gradient. This is indicated by the invertibility of the matrix.
1954 * If the matrix is not invertible, then the present
1955 * cell had an insufficient number of active neighbors. In contrast to
1956 * all previous cases (where we raised exceptions) this is, however,
1957 * not a programming error: it is a runtime error that can happen in
1958 * optimized mode even if it ran well in debug mode, so it is
1959 * reasonable to try to catch this error also in optimized mode. For
1960 * this case, there is the <code>AssertThrow</code> macro: it checks
1961 * the condition like the <code>Assert</code> macro, but not only in
1962 * debug mode; it then outputs an error message, but instead of
1963 * aborting the program as in the case of the <code>Assert</code>
1964 * macro, the exception is thrown using the <code>throw</code> command
1965 * of C++. This way, one has the possibility to catch this error and
1966 * take reasonable counter actions. One such measure would be to
1967 * refine the grid globally, as the case of insufficient directions
1968 * can not occur if every cell of the initial grid has been refined at
1972 * AssertThrow(determinant(Y) != 0, ExcInsufficientDirections());
1976 * If, on the other hand, the matrix is invertible, then invert it,
1977 * multiply the other quantity with it, and compute the estimated error
1978 * using this quantity and the correct powers of the mesh width:
1981 * const Tensor<2, dim> Y_inverse = invert(Y);
1983 * const Tensor<1, dim> gradient = Y_inverse * projected_gradient;
1987 * The last part of this function is the one where we write into
1988 * the element of the output vector what we have just
1989 * computed. The address of this vector has been stored in the
1990 * scratch data object, and all we have to do is know how to get
1991 * at the correct element inside this vector -- but we can ask the
1992 * cell we're on the how-manyth active cell it is
for this:
1995 * scratch_data.error_per_cell(cell->active_cell_index()) =
2004 * <a name=
"step_9-Mainfunction"></a>
2005 * <h3>Main function</h3>
2009 * The <code>main</code> function is similar to the previous examples. The
2010 * primary difference is that we use
MultithreadInfo to set the maximum
2011 * number of threads (see the documentation topic @ref threads
2012 *
"Parallel computing with multiple processors accessing shared memory"
2013 *
for more information). The number of threads used is the minimum of the
2014 * environment variable DEAL_II_NUM_THREADS and the parameter of
2015 * <code>set_thread_limit</code>. If no
value is given to
2016 * <code>set_thread_limit</code>, the
default value from the Intel Threading
2017 * Building Blocks (TBB) library is used. If the call to
2018 * <code>set_thread_limit</code> is omitted, the number of threads will be
2019 * chosen by TBB independently of DEAL_II_NUM_THREADS.
2024 *
using namespace dealii;
2029 * Step9::AdvectionProblem<2> advection_problem_2d;
2030 * advection_problem_2d.run();
2032 *
catch (std::exception &exc)
2034 * std::cerr << std::endl
2036 * <<
"----------------------------------------------------"
2038 * std::cerr <<
"Exception on processing: " << std::endl
2039 * << exc.what() << std::endl
2040 * <<
"Aborting!" << std::endl
2041 * <<
"----------------------------------------------------"
2047 * std::cerr << std::endl
2049 * <<
"----------------------------------------------------"
2051 * std::cerr <<
"Unknown exception!" << std::endl
2052 * <<
"Aborting!" << std::endl
2053 * <<
"----------------------------------------------------"
2061<a name=
"step_9-Results"></a><h1>Results</h1>
2065The results of
this program are not particularly spectacular. They
2066consist of the console output, some grid files, and the solution on
2067each of these grids. First
for the console output:
2070 Number of active cells: 64
2071 Number of degrees of freedom: 1681
2072 Iterations required
for convergence: 338
2073 Norm of residual at convergence: 2.9177e-11
2074 Grid written to grid-0.
vtu
2075 Solution written to solution-0.
vtu
2077 Number of active cells: 121
2078 Number of degrees of freedom: 3436
2079 Iterations required
for convergence: 448
2080 Norm of residual at convergence: 2.40051e-11
2081 Grid written to grid-1.
vtu
2082 Solution written to solution-1.
vtu
2084 Number of active cells: 238
2085 Number of degrees of freedom: 6487
2086 Iterations required
for convergence: 535
2087 Norm of residual at convergence: 2.06897e-11
2088 Grid written to grid-2.
vtu
2089 Solution written to solution-2.
vtu
2091 Number of active cells: 481
2092 Number of degrees of freedom: 13510
2093 Iterations required
for convergence: 669
2094 Norm of residual at convergence: 1.41502e-11
2095 Grid written to grid-3.
vtu
2096 Solution written to solution-3.
vtu
2098 Number of active cells: 958
2099 Number of degrees of freedom: 26137
2100 Iterations required
for convergence: 1039
2101 Norm of residual at convergence: 1.27042e-11
2102 Grid written to grid-4.
vtu
2103 Solution written to solution-4.
vtu
2105 Number of active cells: 1906
2106 Number of degrees of freedom: 52832
2107 Iterations required
for convergence: 1345
2108 Norm of residual at convergence: 1.00694e-11
2109 Grid written to grid-5.
vtu
2110 Solution written to solution-5.
vtu
2112 Number of active cells: 3829
2113 Number of degrees of freedom: 104339
2114 Iterations required
for convergence: 1976
2115 Norm of residual at convergence: 7.43452e-12
2116 Grid written to grid-6.
vtu
2117 Solution written to solution-6.
vtu
2119 Number of active cells: 7414
2120 Number of degrees of freedom: 201946
2121 Iterations required
for convergence: 2256
2122 Norm of residual at convergence: 7.88403e-12
2123 Grid written to grid-7.
vtu
2124 Solution written to solution-7.
vtu
2126 Number of active cells: 14413
2127 Number of degrees of freedom: 389558
2128 Iterations required
for convergence: 2968
2129 Norm of residual at convergence: 6.72725e-12
2130 Grid written to grid-8.
vtu
2131 Solution written to solution-8.
vtu
2133 Number of active cells: 28141
2134 Number of degrees of freedom: 750187
2135 Iterations required
for convergence: 3885
2136 Norm of residual at convergence: 5.86246e-12
2137 Grid written to grid-9.
vtu
2138 Solution written to solution-9.
vtu
2141Quite a number of cells are used on the finest
level to resolve the features of
2142the solution. Here are the fourth and tenth grids:
2143<div
class=
"twocolumn" style=
"width: 80%">
2145 <img src=
"https://www.dealii.org/images/steps/developer/step-9-grid-3.png"
2146 alt=
"Fourth grid in the refinement cycle, showing some adaptivity to features."
2147 width=
"400" height=
"400">
2150 <img src=
"https://www.dealii.org/images/steps/developer/step-9-grid-9.png"
2151 alt=
"Tenth grid in the refinement cycle, showing that the waves are fully captured."
2152 width=
"400" height=
"400">
2155and the fourth and tenth solutions:
2156<div
class=
"twocolumn" style=
"width: 80%">
2158 <img src=
"https://www.dealii.org/images/steps/developer/step-9-solution-3.png"
2159 alt=
"Fourth solution, showing that we resolve most features but some
2160 are sill unresolved and appear blury."
2161 width=
"400" height=
"400">
2164 <img src=
"https://www.dealii.org/images/steps/developer/step-9-solution-9.png"
2165 alt=
"Tenth solution, showing a fully resolved flow."
2166 width=
"400" height=
"400">
2169and both the grid and solution zoomed in:
2170<div
class=
"twocolumn" style=
"width: 80%">
2172 <img src=
"https://www.dealii.org/images/steps/developer/step-9-solution-3-zoom.png"
2173 alt=
"Detail of the fourth solution, showing that we resolve most
2174 features but some are sill unresolved and appear blury. In particular,
2175 the larger cells need to be refined."
2176 width=
"400" height=
"400">
2179 <img src=
"https://www.dealii.org/images/steps/developer/step-9-solution-9-zoom.png"
2180 alt=
"Detail of the tenth solution, showing that we needed a lot more
2181 cells than were present in the fourth solution."
2182 width=
"400" height=
"400">
2186The solution is created by that part that is transported along the wiggly
2187advection field from the left and lower boundaries to the top right, and the
2188part that is created by the source in the lower left corner, and the results of
2189which are also transported along. The grid shown above is well-adapted to
2190resolve these features. The comparison between plots shows that, even though we
2191are
using a high-order approximation, we still need adaptive mesh refinement to
2192fully resolve the wiggles.
2195<a name=
"step_9-PlainProg"></a>
2196<h1> The plain program</h1>
void reinit(const TriaIterator< DoFCellAccessor< dim, spacedim, level_dof_access > > &cell)
static void set_thread_limit(const unsigned int max_threads=numbers::invalid_unsigned_int)
void initialize(const MatrixType &A, const AdditionalData ¶meters=AdditionalData())
#define Assert(cond, exc)
typename ActiveSelector::active_cell_iterator active_cell_iterator
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())
@ update_values
Shape function values.
@ update_normal_vectors
Normal vectors.
@ update_JxW_values
Transformed quadrature weights.
@ update_gradients
Shape function gradients.
@ update_quadrature_points
Transformed quadrature points.
std::vector< index_type > data
void refine_and_coarsen_fixed_number(Triangulation< dim, spacedim > &triangulation, const Vector< Number > &criteria, const double top_fraction_of_cells, const double bottom_fraction_of_cells, const unsigned int max_n_cells=std::numeric_limits< unsigned int >::max())
@ matrix
Contents is actually a matrix.
@ symmetric
Matrix is symmetric.
@ general
No special properties.
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
VectorType::value_type * end(VectorType &V)
T sum(const T &t, const MPI_Comm mpi_communicator)
void run(const std::vector< std::vector< Iterator > > &colored_iterators, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
bool check(const ConstraintKinds kind_in, const unsigned int dim)
void copy(const T *begin, const T *end, U *dest)
int(&) functions(const void *v1, const void *v2)
void assemble(const MeshWorker::DoFInfoBox< dim, DOFINFO > &dinfo, A *assembler)
void reinit(MatrixBlock< MatrixType > &v, const BlockSparsityPattern &p)
::VectorizedArray< Number, width > pow(const ::VectorizedArray< Number, width > &, const Number p)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
DEAL_II_HOST constexpr Number determinant(const SymmetricTensor< 2, dim, Number > &)