613 *
class BoundaryValues :
public Function<dim>
617 * std::vector<double> &values,
618 *
const unsigned int = 0) const override
622 *
for (
unsigned int i = 0; i <
values.size(); ++i)
624 *
if (points[i][0] < 0.5)
639 * The flow field is chosen to be a quarter circle with counterclockwise
640 * flow direction and with the origin as midpoint
for the right half of the
641 * domain with
positive @f$x@f$
values, whereas the flow simply goes to the left
642 * in the left part of the domain at a velocity that matches the one coming
643 * in from the right. In the circular part the magnitude of the flow
644 * velocity is proportional to the distance from the origin. This is a
645 * difference to @ref step_12
"step-12", where the magnitude was 1 everywhere. the
new
646 * definition leads to a linear variation of @f$\beta@f$ along each given face
647 * of a cell. On the other hand, the solution @f$u(x,y)@f$ is exactly the same
651 *
void value_list(
const std::vector<
Point<dim>> &points,
656 *
for (
unsigned int i = 0; i < points.size(); ++i)
658 *
if (points[i][0] > 0)
660 *
values[i][0] = -points[i][1];
661 *
values[i][1] = points[i][0];
666 *
values[i][0] = -points[i][1];
677 * <a name=
"step_30-ClassDGTransportEquation"></a>
678 * <h3>Class: DGTransportEquation</h3>
682 * This declaration of
this class is utterly unaffected by our current
687 *
class DGTransportEquation
690 * DGTransportEquation();
708 *
const Beta<dim> beta_function;
709 *
const RHS<dim> rhs_function;
710 *
const BoundaryValues<dim> boundary_function;
717 * Likewise, the constructor of the
class as well as the
functions
718 * assembling the terms corresponding to cell interiors and boundary faces
719 * are unchanged from before. The function that assembles face terms between
720 * cells also did not change because all it does is operate on two objects
722 * and
FESubfaceValues). Where these objects come from, i.e. how they are
723 * initialized, is of no concern to
this function: it simply assumes that
724 * the quadrature points on faces or subfaces represented by the two objects
725 * correspond to the same points in physical space.
729 * DGTransportEquation<dim>::DGTransportEquation()
732 * , boundary_function()
738 *
void DGTransportEquation<dim>::assemble_cell_term(
745 * std::vector<Point<dim>> beta(fe_v.n_quadrature_points);
746 * std::vector<double> rhs(fe_v.n_quadrature_points);
748 * beta_function.value_list(fe_v.get_quadrature_points(), beta);
749 * rhs_function.value_list(fe_v.get_quadrature_points(), rhs);
751 *
for (
unsigned int point = 0;
point < fe_v.n_quadrature_points; ++
point)
752 *
for (
unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)
754 *
for (
unsigned int j = 0; j < fe_v.dofs_per_cell; ++j)
755 * ui_vi_matrix(i, j) -= beta[
point] * fe_v.shape_grad(i, point) *
756 * fe_v.shape_value(j, point) * JxW[
point];
759 * rhs[
point] * fe_v.shape_value(i, point) * JxW[
point];
765 *
void DGTransportEquation<dim>::assemble_boundary_term(
770 *
const std::vector<double> &JxW = fe_v.get_JxW_values();
771 *
const std::vector<Tensor<1, dim>> &normals = fe_v.get_normal_vectors();
773 * std::vector<Point<dim>> beta(fe_v.n_quadrature_points);
774 * std::vector<double> g(fe_v.n_quadrature_points);
776 * beta_function.value_list(fe_v.get_quadrature_points(), beta);
777 * boundary_function.value_list(fe_v.get_quadrature_points(), g);
779 *
for (
unsigned int point = 0;
point < fe_v.n_quadrature_points; ++
point)
781 *
const double beta_n = beta[
point] * normals[
point];
783 *
for (
unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)
784 *
for (
unsigned int j = 0; j < fe_v.dofs_per_cell; ++j)
785 * ui_vi_matrix(i, j) += beta_n * fe_v.shape_value(j, point) *
786 * fe_v.shape_value(i, point) * JxW[
point];
788 *
for (
unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)
790 * beta_n * g[
point] * fe_v.shape_value(i, point) * JxW[
point];
796 *
void DGTransportEquation<dim>::assemble_face_term(
804 *
const std::vector<double> &JxW = fe_v.get_JxW_values();
805 *
const std::vector<Tensor<1, dim>> &normals = fe_v.get_normal_vectors();
807 * std::vector<Point<dim>> beta(fe_v.n_quadrature_points);
809 * beta_function.value_list(fe_v.get_quadrature_points(), beta);
811 *
for (
unsigned int point = 0;
point < fe_v.n_quadrature_points; ++
point)
813 *
const double beta_n = beta[
point] * normals[
point];
816 *
for (
unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)
817 *
for (
unsigned int j = 0; j < fe_v.dofs_per_cell; ++j)
818 * ui_vi_matrix(i, j) += beta_n * fe_v.shape_value(j, point) *
819 * fe_v.shape_value(i, point) * JxW[
point];
821 *
for (
unsigned int k = 0; k < fe_v_neighbor.dofs_per_cell; ++k)
822 *
for (
unsigned int j = 0; j < fe_v.dofs_per_cell; ++j)
823 * ui_ve_matrix(k, j) -= beta_n * fe_v.shape_value(j, point) *
824 * fe_v_neighbor.shape_value(k, point) *
829 *
for (
unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)
830 *
for (
unsigned int l = 0;
l < fe_v_neighbor.dofs_per_cell; ++
l)
831 * ue_vi_matrix(i, l) += beta_n *
832 * fe_v_neighbor.shape_value(l, point) *
833 * fe_v.shape_value(i, point) * JxW[
point];
835 *
for (
unsigned int k = 0; k < fe_v_neighbor.dofs_per_cell; ++k)
836 *
for (
unsigned int l = 0;
l < fe_v_neighbor.dofs_per_cell; ++
l)
837 * ue_ve_matrix(k, l) -=
838 * beta_n * fe_v_neighbor.shape_value(l, point) *
839 * fe_v_neighbor.shape_value(k, point) * JxW[
point];
848 * <a name=
"step_30-ClassDGMethod"></a>
849 * <h3>Class: DGMethod</h3>
853 * This declaration is much like that of @ref step_12
"step-12". However, we introduce a
854 *
new routine (set_anisotropic_flags) and modify another one (refine_grid).
861 * DGMethod(
const bool anisotropic);
866 *
void setup_system();
867 *
void assemble_system();
869 *
void refine_grid();
870 *
void set_anisotropic_flags();
871 *
void output_results(
const unsigned int cycle)
const;
877 * Again we want to use DG elements of degree 1 (but
this is only
878 * specified in the constructor). If you want to use a DG method of a
879 * different degree replace 1 in the constructor by the
new degree.
882 *
const unsigned int degree;
890 * This is
new, the threshold
value used in the evaluation of the
891 * anisotropic jump indicator explained in the introduction. Its
value is
892 * set to 3.0 in the constructor, but it can easily be changed to a
893 * different
value greater than 1.
896 *
const double anisotropic_threshold_ratio;
899 * This is a
bool flag indicating whether anisotropic refinement shall be
900 * used or not. It is set by the constructor, which takes an argument of
904 *
const bool anisotropic;
907 *
const QGauss<dim - 1> face_quadrature;
912 *
const DGTransportEquation<dim> dg;
917 * DGMethod<dim>::DGMethod(
const bool anisotropic)
921 * Change here
for DG methods of different degrees.
927 * , anisotropic_threshold_ratio(3.)
928 * , anisotropic(anisotropic)
931 * As beta is a linear function, we can choose the degree of the
932 * quadrature
for which the resulting integration is correct. Thus, we
933 * choose to use <code>degree+1</code> Gauss points, which enables us to
934 * integrate exactly polynomials of degree <code>2*degree+1</code>, enough
935 *
for all the integrals we will perform in
this program.
938 * , quadrature(degree + 1)
939 * , face_quadrature(degree + 1)
946 *
void DGMethod<dim>::setup_system()
948 * dof_handler.distribute_dofs(fe);
949 * sparsity_pattern.
reinit(dof_handler.n_dofs(),
950 * dof_handler.n_dofs(),
954 * fe.n_dofs_per_cell());
958 * sparsity_pattern.compress();
960 * system_matrix.reinit(sparsity_pattern);
962 * solution2.reinit(dof_handler.n_dofs());
963 * right_hand_side.reinit(dof_handler.n_dofs());
970 * <a name=
"step_30-Functionassemble_system"></a>
971 * <h4>
Function: assemble_system</h4>
975 * We proceed with the <code>assemble_system</code> function that implements
976 * the DG discretization. This function does the same thing as the
977 * <code>assemble_system</code> function from @ref step_12
"step-12" (but without
978 *
MeshWorker). The four cases considered
for the neighbor-relations of a
979 * cell are the same as the isotropic
case, namely a) cell is at the
980 * boundary,
b) there are finer neighboring cells, c) the neighbor is
981 * neither coarser nor finer and
d) the neighbor is coarser. However, the
982 * way in which we decide upon which
case we have are modified in the way
983 * described in the introduction.
987 *
void DGMethod<dim>::assemble_system()
989 *
const unsigned int dofs_per_cell = dof_handler.get_fe().n_dofs_per_cell();
990 * std::vector<types::global_dof_index> dofs(dofs_per_cell);
991 * std::vector<types::global_dof_index> dofs_neighbor(dofs_per_cell);
1003 *
FEValues<dim> fe_v(mapping, fe, quadrature, update_flags);
1007 * face_update_flags);
1011 * face_update_flags);
1015 * neighbor_face_update_flags);
1026 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1031 * fe_v.reinit(cell);
1033 * dg.assemble_cell_term(fe_v, ui_vi_matrix, cell_vector);
1035 * cell->get_dof_indices(dofs);
1037 *
for (
const auto face_no : cell->face_indices())
1039 * const auto face = cell->face(face_no);
1043 * Case (a): The face is at the boundary.
1046 * if (face->at_boundary())
1048 * fe_v_face.
reinit(cell, face_no);
1050 * dg.assemble_boundary_term(fe_v_face, ui_vi_matrix, cell_vector);
1055 * ExcInternalError());
1056 *
const auto neighbor = cell->neighbor(face_no);
1060 * Case (b): This is an
internal face and the neighbor
1061 * is refined (which we can test by asking whether the
1062 * face of the current cell has children). In this
1063 * case, we will need to integrate over the
1064 *
"sub-faces", i.
e., the children of the face of the
1069 * (There is a slightly confusing corner case: If we
1070 * are in 1
d -- where admittedly the current program
1071 * and its demonstration of anisotropic refinement is
1072 * not particularly relevant -- then the faces between
1073 * cells are
always the same: they are just
1074 * vertices. In other words, in 1
d, we do not want to
1075 * treat faces between cells of different
level
1076 * differently. The condition `face->has_children()`
1077 * we
check here ensures this: in 1
d, this function
1078 *
always returns `false`, and consequently in 1
d we
1079 * will not ever go into this `if` branch. But we will
1080 * have to come back to this corner case below in case
1084 * if (face->has_children())
1088 * We need to know, which of the neighbors faces points in
1089 * the direction of our cell. Using the @p
1090 * neighbor_face_no function we get this information for
1091 * both coarser and non-coarser neighbors.
1094 * const unsigned
int neighbor2 =
1095 * cell->neighbor_face_no(face_no);
1099 * Now we
loop over all subfaces, i.e. the children and
1100 * possibly grandchildren of the current face.
1103 *
for (
unsigned int subface_no = 0;
1104 * subface_no < face->n_active_descendants();
1109 * To get the cell behind the current subface we can
1110 * use the @p neighbor_child_on_subface function. it
1111 * takes care of all the complicated situations of
1112 * anisotropic refinement and non-standard faces.
1115 *
const auto neighbor_child =
1116 * cell->neighbor_child_on_subface(face_no, subface_no);
1117 *
Assert(!neighbor_child->has_children(),
1118 * ExcInternalError());
1122 * The remaining part of
this case is unchanged.
1129 * fe_v_subface.reinit(cell, face_no, subface_no);
1130 * fe_v_face_neighbor.reinit(neighbor_child, neighbor2);
1132 * dg.assemble_face_term(fe_v_subface,
1133 * fe_v_face_neighbor,
1139 * neighbor_child->get_dof_indices(dofs_neighbor);
1141 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1142 *
for (
unsigned int j = 0; j < dofs_per_cell; ++j)
1144 * system_matrix.add(dofs[i],
1146 * ue_vi_matrix(i, j));
1147 * system_matrix.add(dofs_neighbor[i],
1149 * ui_ve_matrix(i, j));
1150 * system_matrix.add(dofs_neighbor[i],
1152 * ue_ve_matrix(i, j));
1160 * Case (c). We get here
if this is an
internal
1161 * face and
if the neighbor is not further refined
1162 * (or, as mentioned above, we are in 1
d in which
1163 *
case we get here
for every
internal face). We
1164 * then need to decide whether we want to
1165 * integrate over the current face. If the
1166 * neighbor is in fact coarser, then we ignore the
1167 * face and instead handle it when we visit the
1168 * neighboring cell and look at the current face
1169 * (except in 1d, where as mentioned above
this is
1173 *
if (dim > 1 && cell->neighbor_is_coarser(face_no))
1178 * On the other hand,
if the neighbor is more
1179 * refined, then we have already handled the face
1180 * in
case (b) above (except in 1d). So
for 2
d and
1181 * 3
d, we just have to decide whether we want to
1182 * handle a face between cells at the same
level
1183 * from the current side or from the neighboring
1184 * side. We
do this by introducing a tie-breaker:
1185 * We
'll just take the cell with the smaller index
1186 * (within the current refinement level). In 1d,
1187 * we take either the coarser cell, or if they are
1188 * on the same level, the one with the smaller
1189 * index within that level. This leads to a
1190 * complicated condition that, hopefully, makes
1191 * sense given the description above:
1194 * if (((dim > 1) && (cell->index() < neighbor->index())) ||
1195 * ((dim == 1) && ((cell->level() < neighbor->level()) ||
1196 * ((cell->level() == neighbor->level()) &&
1197 * (cell->index() < neighbor->index())))))
1201 * Here we know, that the neighbor is not coarser so we
1202 * can use the usual @p neighbor_of_neighbor
1203 * function. However, we could also use the more
1204 * general @p neighbor_face_no function.
1207 * const unsigned int neighbor2 =
1208 * cell->neighbor_of_neighbor(face_no);
1214 * fe_v_face.reinit(cell, face_no);
1215 * fe_v_face_neighbor.reinit(neighbor, neighbor2);
1217 * dg.assemble_face_term(fe_v_face,
1218 * fe_v_face_neighbor,
1224 * neighbor->get_dof_indices(dofs_neighbor);
1226 * for (unsigned int i = 0; i < dofs_per_cell; ++i)
1227 * for (unsigned int j = 0; j < dofs_per_cell; ++j)
1229 * system_matrix.add(dofs[i],
1231 * ue_vi_matrix(i, j));
1232 * system_matrix.add(dofs_neighbor[i],
1234 * ui_ve_matrix(i, j));
1235 * system_matrix.add(dofs_neighbor[i],
1237 * ue_ve_matrix(i, j));
1243 * We do not need to consider a case (d), as those
1244 * faces are treated 'from the other side within
1252 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1253 *
for (
unsigned int j = 0; j < dofs_per_cell; ++j)
1254 * system_matrix.add(dofs[i], dofs[j], ui_vi_matrix(i, j));
1256 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1257 * right_hand_side(dofs[i]) += cell_vector(i);
1265 * <a name=
"step_30-Solver"></a>
1270 * For
this simple problem we use the simple Richardson iteration again. The
1271 * solver is completely unaffected by our anisotropic changes.
1274 *
template <
int dim>
1282 * preconditioner.
initialize(system_matrix, fe.n_dofs_per_cell());
1284 * solver.solve(system_matrix, solution, right_hand_side, preconditioner);
1291 * <a name=
"step_30-Refinement"></a>
1292 * <h3>Refinement</h3>
1296 * We
refine the grid according to the same simple refinement criterion used
1297 * in @ref step_12
"step-12", namely an approximation to the
gradient of the solution.
1300 *
template <
int dim>
1301 *
void DGMethod<dim>::refine_grid()
1313 * gradient_indicator);
1317 * and
scale it to obtain an error indicator.
1320 *
for (
const auto &cell :
triangulation.active_cell_iterators())
1321 * gradient_indicator[cell->active_cell_index()] *=
1325 * Then we use
this indicator to flag the 30 percent of the cells with
1326 * highest error indicator to be refined.
1330 * gradient_indicator,
1335 * Now the refinement flags are set
for those cells with a large error
1336 * indicator. If
nothing is done to change
this, those cells will be
1337 * refined isotropically. If the @p anisotropic flag given to
this
1338 * function is set, we now call the set_anisotropic_flags() function,
1339 * which uses the jump indicator to reset some of the refinement flags to
1340 * anisotropic refinement.
1344 * set_anisotropic_flags();
1347 * Now execute the refinement considering anisotropic as well as isotropic
1356 * Once an error indicator has been evaluated and the cells with largest
1357 * error are flagged for refinement we want to loop over the flagged cells
1358 * again to decide whether they need isotropic refinement or whether
1359 * anisotropic refinement is more appropriate. This is the anisotropic jump
1360 * indicator explained in the introduction.
1363 * template <
int dim>
1364 *
void DGMethod<dim>::set_anisotropic_flags()
1368 * We want to evaluate the jump over faces of the flagged cells, so we
1369 * need some objects to evaluate
values of the solution on faces.
1378 * face_update_flags);
1382 * face_update_flags);
1390 * Now we need to
loop over all active cells.
1393 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1396 * We only need to consider cells which are flagged for refinement.
1399 * if (cell->refine_flag_set())
1401 *
std::array<double, dim> jump_in_coordinate_direction;
1402 * std::array<double, dim> face_area_in_coordinate_direction;
1404 *
for (
const auto face_no : cell->face_indices())
1406 * const auto face = cell->face(face_no);
1408 *
if (!face->at_boundary())
1410 *
Assert(cell->neighbor(face_no).state() ==
1412 * ExcInternalError());
1413 *
const auto neighbor = cell->neighbor(face_no);
1415 * std::vector<double> u(fe_v_face.n_quadrature_points);
1416 * std::vector<double> u_neighbor(fe_v_face.n_quadrature_points);
1420 * The four cases of different neighbor relations seen in
1421 * the assembly routines are repeated much in the same way
1425 *
if (face->has_children())
1429 * The neighbor is refined. First we store the
1430 * information, which of the neighbor
's faces points in
1431 * the direction of our current cell. This property is
1432 * inherited to the children.
1435 * unsigned int neighbor2 = cell->neighbor_face_no(face_no);
1438 * Now we loop over all subfaces,
1441 * for (unsigned int subface_no = 0;
1442 * subface_no < face->n_active_descendants();
1447 * get an iterator pointing to the cell behind the
1448 * present subface...
1451 * const auto neighbor_child =
1452 * cell->neighbor_child_on_subface(face_no,
1454 * Assert(!neighbor_child->has_children(),
1455 * ExcInternalError());
1458 * ... and reinit the respective FEFaceValues and
1459 * FESubFaceValues objects.
1462 * fe_v_subface.reinit(cell, face_no, subface_no);
1463 * fe_v_face_neighbor.reinit(neighbor_child, neighbor2);
1466 * We obtain the function values
1469 * fe_v_subface.get_function_values(solution2, u);
1470 * fe_v_face_neighbor.get_function_values(solution2,
1474 * as well as the quadrature weights, multiplied by
1475 * the Jacobian determinant.
1478 * const std::vector<double> &JxW =
1479 * fe_v_subface.get_JxW_values();
1482 * Now we loop over all quadrature points
1485 * for (unsigned int x = 0;
1486 * x < fe_v_subface.n_quadrature_points;
1491 * and integrate the absolute value of the jump
1492 * of the solution, i.e. the absolute value of
1493 * the difference between the function value
1494 * seen from the current cell and the
1495 * neighboring cell, respectively. We know, that
1496 * the first two faces are orthogonal to the
1497 * first coordinate direction on the unit cell,
1498 * the second two faces are orthogonal to the
1499 * second coordinate direction and so on, so we
1500 * accumulate these values into vectors with
1501 * <code>dim</code> components.
1504 * jump_in_coordinate_direction[face_no / 2] +=
1505 * std::abs(u[x] - u_neighbor[x]) * JxW[x];
1508 * We also sum up the scaled weights to obtain
1509 * the measure of the face.
1512 * face_area_in_coordinate_direction[face_no / 2] +=
1519 * if (!cell->neighbor_is_coarser(face_no))
1523 * Our current cell and the neighbor have the same
1524 * refinement along the face under
1525 * consideration. Apart from that, we do much the
1526 * same as with one of the subcells in the above
1530 * unsigned int neighbor2 =
1531 * cell->neighbor_of_neighbor(face_no);
1533 * fe_v_face.reinit(cell, face_no);
1534 * fe_v_face_neighbor.reinit(neighbor, neighbor2);
1536 * fe_v_face.get_function_values(solution2, u);
1537 * fe_v_face_neighbor.get_function_values(solution2,
1540 * const std::vector<double> &JxW =
1541 * fe_v_face.get_JxW_values();
1543 * for (unsigned int x = 0;
1544 * x < fe_v_face.n_quadrature_points;
1547 * jump_in_coordinate_direction[face_no / 2] +=
1548 * std::abs(u[x] - u_neighbor[x]) * JxW[x];
1549 * face_area_in_coordinate_direction[face_no / 2] +=
1553 * else // i.e. neighbor is coarser than cell
1557 * Now the neighbor is actually coarser. This case
1558 * is new, in that it did not occur in the assembly
1559 * routine. Here, we have to consider it, but this
1560 * is not overly complicated. We simply use the @p
1561 * neighbor_of_coarser_neighbor function, which
1562 * again takes care of anisotropic refinement and
1563 * non-standard face orientation by itself.
1566 * std::pair<unsigned int, unsigned int>
1567 * neighbor_face_subface =
1568 * cell->neighbor_of_coarser_neighbor(face_no);
1569 * Assert(neighbor_face_subface.first < cell->n_faces(),
1570 * ExcInternalError());
1571 * Assert(neighbor_face_subface.second <
1572 * neighbor->face(neighbor_face_subface.first)
1573 * ->n_active_descendants(),
1574 * ExcInternalError());
1575 * Assert(neighbor->neighbor_child_on_subface(
1576 * neighbor_face_subface.first,
1577 * neighbor_face_subface.second) == cell,
1578 * ExcInternalError());
1580 * fe_v_face.reinit(cell, face_no);
1581 * fe_v_subface.reinit(neighbor,
1582 * neighbor_face_subface.first,
1583 * neighbor_face_subface.second);
1585 * fe_v_face.get_function_values(solution2, u);
1586 * fe_v_subface.get_function_values(solution2,
1589 * const std::vector<double> &JxW =
1590 * fe_v_face.get_JxW_values();
1592 * for (unsigned int x = 0;
1593 * x < fe_v_face.n_quadrature_points;
1596 * jump_in_coordinate_direction[face_no / 2] +=
1597 * std::abs(u[x] - u_neighbor[x]) * JxW[x];
1598 * face_area_in_coordinate_direction[face_no / 2] +=
1607 * Now we analyze the size of the mean jumps, which we get dividing
1608 * the jumps by the measure of the respective faces.
1611 * std::array<double, dim> average_jumps;
1612 * double sum_of_average_jumps = 0.;
1613 * for (unsigned int i = 0; i < dim; ++i)
1615 * average_jumps[i] = jump_in_coordinate_direction[i] /
1616 * face_area_in_coordinate_direction[i];
1617 * sum_of_average_jumps += average_jumps[i];
1622 * Now we loop over the <code>dim</code> coordinate directions of
1623 * the unit cell and compare the average jump over the faces
1624 * orthogonal to that direction with the average jumps over faces
1625 * orthogonal to the remaining direction(s). If the first is larger
1626 * than the latter by a given factor, we refine only along hat
1627 * axis. Otherwise we leave the refinement flag unchanged, resulting
1628 * in isotropic refinement.
1631 * for (unsigned int i = 0; i < dim; ++i)
1632 * if (average_jumps[i] > anisotropic_threshold_ratio *
1633 * (sum_of_average_jumps - average_jumps[i]))
1634 * cell->set_refine_flag(RefinementCase<dim>::cut_axis(i));
1641 * <a name="step_30-TheRest"></a>
1646 * The remaining part of the program very much follows the scheme of
1647 * previous tutorial programs. We output the mesh in VTU format (just
1648 * as we did in @ref step_1 "step-1", for example), and the visualization output
1649 * in VTU format as we almost always do.
1652 * template <int dim>
1653 * void DGMethod<dim>::output_results(const unsigned int cycle) const
1655 * std::string refine_type;
1657 * refine_type = ".aniso";
1659 * refine_type = ".iso";
1662 * const std::string filename =
1663 * "grid-" + std::to_string(cycle) + refine_type + ".svg";
1664 * std::cout << " Writing grid to <" << filename << ">..." << std::endl;
1665 * std::ofstream svg_output(filename);
1668 * grid_out.write_svg(triangulation, svg_output);
1672 * const std::string filename =
1673 * "sol-" + std::to_string(cycle) + refine_type + ".vtu";
1674 * std::cout << " Writing solution to <" << filename << ">..."
1676 * std::ofstream gnuplot_output(filename);
1678 * DataOut<dim> data_out;
1679 * data_out.attach_dof_handler(dof_handler);
1680 * data_out.add_data_vector(solution2, "u");
1682 * data_out.build_patches(degree);
1684 * data_out.write_vtu(gnuplot_output);
1690 * template <int dim>
1691 * void DGMethod<dim>::run()
1693 * for (unsigned int cycle = 0; cycle < 6; ++cycle)
1695 * std::cout << "Cycle " << cycle << ':
' << std::endl;
1701 * Create the rectangular domain.
1704 * Point<dim> p1, p2;
1706 * for (unsigned int i = 0; i < dim; ++i)
1710 * Adjust the number of cells in different directions to obtain
1711 * completely isotropic cells for the original mesh.
1714 * std::vector<unsigned int> repetitions(dim, 1);
1715 * repetitions[0] = 2;
1716 * GridGenerator::subdivided_hyper_rectangle(triangulation,
1721 * triangulation.refine_global(5 - dim);
1727 * std::cout << " Number of active cells: "
1728 * << triangulation.n_active_cells() << std::endl;
1732 * std::cout << " Number of degrees of freedom: " << dof_handler.n_dofs()
1735 * Timer assemble_timer;
1736 * assemble_system();
1737 * std::cout << " Time of assemble_system: " << assemble_timer.cpu_time()
1741 * output_results(cycle);
1743 * std::cout << std::endl;
1746 * } // namespace Step30
1754 * using namespace Step30;
1758 * If you want to run the program in 3d, simply change the following
1759 * line to <code>const unsigned int dim = 3;</code>.
1762 * const unsigned int dim = 2;
1767 * First, we perform a run with isotropic refinement.
1770 * std::cout << "Performing a " << dim
1771 * << "D run with isotropic refinement..." << std::endl
1772 * << "------------------------------------------------"
1774 * DGMethod<dim> dgmethod_iso(false);
1775 * dgmethod_iso.run();
1781 * Now we do a second run, this time with anisotropic refinement.
1784 * std::cout << std::endl
1785 * << "Performing a " << dim
1786 * << "D run with anisotropic refinement..." << std::endl
1787 * << "--------------------------------------------------"
1789 * DGMethod<dim> dgmethod_aniso(true);
1790 * dgmethod_aniso.run();
1793 * catch (std::exception &exc)
1795 * std::cerr << std::endl
1797 * << "----------------------------------------------------"
1799 * std::cerr << "Exception on processing: " << std::endl
1800 * << exc.what() << std::endl
1801 * << "Aborting!" << std::endl
1802 * << "----------------------------------------------------"
1808 * std::cerr << std::endl
1810 * << "----------------------------------------------------"
1812 * std::cerr << "Unknown exception!" << std::endl
1813 * << "Aborting!" << std::endl
1814 * << "----------------------------------------------------"
1822<a name="step_30-Results"></a><h1>Results</h1>
1826The output of this program consist of the console output, the SVG
1827files containing the grids, and the solutions given in VTU format.
1829Performing a 2D run with isotropic refinement...
1830------------------------------------------------
1832 Number of active cells: 128
1833 Number of degrees of freedom: 512
1834 Time of assemble_system: 0.092049
1835 Writing grid to <grid-0.iso.svg>...
1836 Writing solution to <sol-0.iso.vtu>...
1839 Number of active cells: 239
1840 Number of degrees of freedom: 956
1841 Time of assemble_system: 0.109519
1842 Writing grid to <grid-1.iso.svg>...
1843 Writing solution to <sol-1.iso.vtu>...
1846 Number of active cells: 491
1847 Number of degrees of freedom: 1964
1848 Time of assemble_system: 0.08303
1849 Writing grid to <grid-2.iso.svg>...
1850 Writing solution to <sol-2.iso.vtu>...
1853 Number of active cells: 1031
1854 Number of degrees of freedom: 4124
1855 Time of assemble_system: 0.278987
1856 Writing grid to <grid-3.iso.svg>...
1857 Writing solution to <sol-3.iso.vtu>...
1860 Number of active cells: 2027
1861 Number of degrees of freedom: 8108
1862 Time of assemble_system: 0.305869
1863 Writing grid to <grid-4.iso.svg>...
1864 Writing solution to <sol-4.iso.vtu>...
1867 Number of active cells: 4019
1868 Number of degrees of freedom: 16076
1869 Time of assemble_system: 0.47616
1870 Writing grid to <grid-5.iso.svg>...
1871 Writing solution to <sol-5.iso.vtu>...
1874Performing a 2D run with anisotropic refinement...
1875--------------------------------------------------
1877 Number of active cells: 128
1878 Number of degrees of freedom: 512
1879 Time of assemble_system: 0.052866
1880 Writing grid to <grid-0.aniso.svg>...
1881 Writing solution to <sol-0.aniso.vtu>...
1884 Number of active cells: 171
1885 Number of degrees of freedom: 684
1886 Time of assemble_system: 0.050917
1887 Writing grid to <grid-1.aniso.svg>...
1888 Writing solution to <sol-1.aniso.vtu>...
1891 Number of active cells: 255
1892 Number of degrees of freedom: 1020
1893 Time of assemble_system: 0.064132
1894 Writing grid to <grid-2.aniso.svg>...
1895 Writing solution to <sol-2.aniso.vtu>...
1898 Number of active cells: 394
1899 Number of degrees of freedom: 1576
1900 Time of assemble_system: 0.119849
1901 Writing grid to <grid-3.aniso.svg>...
1902 Writing solution to <sol-3.aniso.vtu>...
1905 Number of active cells: 648
1906 Number of degrees of freedom: 2592
1907 Time of assemble_system: 0.218244
1908 Writing grid to <grid-4.aniso.svg>...
1909 Writing solution to <sol-4.aniso.vtu>...
1912 Number of active cells: 1030
1913 Number of degrees of freedom: 4120
1914 Time of assemble_system: 0.128121
1915 Writing grid to <grid-5.aniso.svg>...
1916 Writing solution to <sol-5.aniso.vtu>...
1919This text output shows the reduction in the number of cells which results from
1920the successive application of anisotropic refinement. After the last refinement
1921step the savings have accumulated so much that almost four times as many cells
1922and thus degrees of freedom are needed in the isotropic case. The time needed for assembly
1923scales with a similar factor.
1925The first interesting part is of course to see how the meshes look like.
1926On the left are the isotropically refined ones, on the right the
1927anisotropic ones (colors indicate the refinement level of cells):
1929<table width="80%" align="center">
1932 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-0.iso.9.2.png" alt="">
1935 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-0.aniso.9.2.png" alt="">
1940 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-1.iso.9.2.png" alt="">
1943 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-1.aniso.9.2.png" alt="">
1948 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-2.iso.9.2.png" alt="">
1951 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-2.aniso.9.2.png" alt="">
1956 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-3.iso.9.2.png" alt="">
1959 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-3.aniso.9.2.png" alt="">
1964 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-4.iso.9.2.png" alt="">
1967 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-4.aniso.9.2.png" alt="">
1972 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-5.iso.9.2.png" alt="">
1975 <img src="https://www.dealii.org/images/steps/developer/step-30.grid-5.aniso.9.2.png" alt="">
1981The other interesting thing is, of course, to see the solution on these
1982two sequences of meshes. Here they are, on the refinement cycles 1 and 4,
1983clearly showing that the solution is indeed composed of <i>discontinuous</i> piecewise
1986<table width="60%" align="center">
1989 <img src="https://www.dealii.org/images/steps/developer/step-30.sol-1.iso.9.2.png" alt="">
1992 <img src="https://www.dealii.org/images/steps/developer/step-30.sol-1.aniso.9.2.png" alt="">
1997 <img src="https://www.dealii.org/images/steps/developer/step-30.sol-4.iso.9.2.png" alt="">
2000 <img src="https://www.dealii.org/images/steps/developer/step-30.sol-4.aniso.9.2.png" alt="">
2005We see, that the solution on the anisotropically refined mesh is very similar to
2006the solution obtained on the isotropically refined mesh. Thus the anisotropic
2007indicator seems to effectively select the appropriate cells for anisotropic
2010The pictures also explain why the mesh is refined as it is.
2011In the whole left part of the domain refinement is only performed along the
2012@f$y@f$-axis of cells. In the right part of the domain the refinement is dominated by
2013isotropic refinement, as the anisotropic feature of the solution - the jump from
2014one to zero - is not well aligned with the mesh where the advection direction
2015takes a turn. However, at the bottom and closest (to the observer) parts of the
2016quarter circle this jumps again becomes more and more aligned
2017with the mesh and the refinement algorithm reacts by creating anisotropic cells
2018of increasing aspect ratio.
2020It might seem that the necessary alignment of anisotropic features and the
2021coarse mesh can decrease performance significantly for real world
2022problems. That is not wrong in general: If one were, for example, to apply
2023anisotropic refinement to problems in which shocks appear (e.g., the
2024equations solved in @ref step_69 "step-69"), then it many cases the shock is not aligned
2025with the mesh and anisotropic refinement will help little unless one also
2026introduces techniques to move the mesh in alignment with the shocks.
2027On the other hand, many steep features of solutions are due to boundary layers.
2028In those cases, the mesh is already aligned with the anisotropic features
2029because it is of course aligned with the boundary itself, and anisotropic
2030refinement will almost always increase the efficiency of computations on
2031adapted grids for these cases.
2034<a name="step_30-PlainProg"></a>
2035<h1> The plain program</h1>
2036@include "step-30.cc"
const std::vector< double > & get_JxW_values() const
virtual void value_list(const std::vector< Point< dim > > &points, std::vector< RangeNumberType > &values, const unsigned int component=0) const
void initialize(const MatrixType &A, const AdditionalData parameters)
virtual void reinit(const size_type N, const bool omit_zeroing_entries=false)
#define Assert(cond, exc)
#define AssertDimension(dim1, dim2)
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())
void make_flux_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern)
@ 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.
void approximate(const SynchronousIterators< std::tuple< typename DoFHandler< dim, spacedim >::active_cell_iterator, Vector< float >::iterator > > &cell, const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof_handler, const InputVector &solution, const unsigned int component)
void approximate_gradient(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const InputVector &solution, Vector< float > &derivative_norm, const unsigned int component=0)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
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())
@ valid
Iterator points to a valid object.
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
Tensor< 2, dim, Number > l(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
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)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
bool check(const ConstraintKinds kind_in, const unsigned int dim)
int(&) functions(const void *v1, const void *v2)
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