734 *
const unsigned int )
const
742 *
class RightHandSide :
public Function<dim>
746 *
const unsigned int component = 0)
const override;
752 *
double RightHandSide<dim>::value(
const Point<dim> &p,
753 *
const unsigned int )
const
763 * The
class that
implements the exact pressure solution has an
764 * oddity in that we implement it as a vector-valued
one with two
765 * components. (We say that it has two components in the constructor
766 * where we call the constructor of the base Function class.) In the
767 * `
value()` function, we do not test for the
value of the
768 * `component` argument, which implies that we return the same
value
769 * for both components of the vector-valued function. We do this
770 * because we describe the finite element in use in this program as
771 * a vector-valued system that contains the interior and the
772 * interface pressures, and when we compute errors, we will want to
773 * use the same pressure solution to test both of these components.
777 * class ExactPressure :
public Function<dim>
785 *
const unsigned int component)
const override;
791 *
double ExactPressure<dim>::value(
const Point<dim> &p,
792 *
const unsigned int )
const
820 *
return return_value;
828 * <a name=
"WGDarcyEquationclassimplementation"></a>
829 * <h3>WGDarcyEquation
class implementation</h3>
834 * <a name=
"WGDarcyEquationWGDarcyEquation"></a>
835 * <h4>WGDarcyEquation::WGDarcyEquation</h4>
839 * In
this constructor, we create a finite element space
for vector valued
840 *
functions, which will here include the ones used
for the interior and
841 *
interface pressures, @f$p^\circ@f$ and @f$p^\partial@f$.
845 * WGDarcyEquation<dim>::WGDarcyEquation(
const unsigned int degree)
857 * <a name=
"WGDarcyEquationmake_grid"></a>
858 * <h4>WGDarcyEquation::make_grid</h4>
862 * We generate a mesh on the unit square domain and
refine it.
866 *
void WGDarcyEquation<dim>::make_grid()
871 * std::cout <<
" Number of active cells: " <<
triangulation.n_active_cells()
882 * <a name=
"WGDarcyEquationsetup_system"></a>
883 * <h4>WGDarcyEquation::setup_system</h4>
887 * After we have created the mesh above, we distribute degrees of
888 * freedom and resize matrices and vectors. The only piece of
889 * interest in
this function is how we
interpolate the boundary
890 *
values for the pressure. Since the pressure consists of interior
891 * and
interface components, we need to make sure that we only
892 *
interpolate onto that component of the vector-valued solution
893 * space that corresponds to the
interface pressures (as these are
894 * the only ones that are defined on the boundary of the domain). We
895 *
do this via a component mask
object for only the interface
900 *
void WGDarcyEquation<dim>::setup_system()
902 * dof_handler.distribute_dofs(fe);
903 * dof_handler_dgrt.distribute_dofs(fe_dgrt);
905 * std::cout <<
" Number of pressure degrees of freedom: "
906 * << dof_handler.n_dofs() << std::endl;
908 * solution.reinit(dof_handler.n_dofs());
909 * system_rhs.reinit(dof_handler.n_dofs());
913 * constraints.clear();
919 * BoundaryValues<dim>(),
921 * interface_pressure_mask);
922 * constraints.close();
928 * In the bilinear form, there is no integration term over faces
929 * between two neighboring cells, so we can just use
936 * sparsity_pattern.copy_from(dsp);
938 * system_matrix.reinit(sparsity_pattern);
946 * <a name=
"WGDarcyEquationassemble_system"></a>
947 * <h4>WGDarcyEquation::assemble_system</h4>
951 * This
function is more interesting. As detailed in the
952 * introduction, the assembly of the linear system requires us to
954 * element in the Raviart-Thomas space. As a consequence, we need to
955 * define a Raviart-Thomas finite element object, and have
FEValues
956 * objects that evaluate it at quadrature points. We then need to
957 * compute the
matrix @f$C^K@f$ on every cell @f$K@f$,
for which we need the
958 * matrices @f$M^K@f$ and @f$G^K@f$ mentioned in the introduction.
962 *
A point that may not be obvious is that in all previous tutorial
966 *
extract the
values of a finite element function (represented by a
967 * vector of DoF
values) on the quadrature points of a cell. For
968 * this operation to work,
one needs to know which vector elements
969 * correspond to the degrees of freedom on a given cell -- i.
e.,
970 * exactly the kind of information and operation provided by the
975 * We could create a
DoFHandler object for the "broken" Raviart-Thomas space
976 * (using the FE_DGRT class), but we really don't want to here: At
977 * least in the current function, we have no need for any globally defined
978 * degrees of freedom associated with this broken space, but really only
979 * need to reference the shape
functions of such a space on the current
980 * cell. As a consequence, we use the fact that
one can
call
983 * can of course only provide us with information that only
984 * references information about cells, rather than degrees of freedom
985 * enumerated on these cells. So we can't use
988 * at quadrature points on the current cell. It is this kind of
989 * functionality we will make use of below. The variable that will
990 * give us this information about the Raviart-Thomas
functions below
991 * is then the `fe_values_rt` (and corresponding `fe_face_values_rt`)
996 * Given this introduction, the following declarations should be
1000 * template <
int dim>
1001 *
void WGDarcyEquation<dim>::assemble_system()
1003 *
const QGauss<dim> quadrature_formula(fe_dgrt.degree + 1);
1004 *
const QGauss<dim - 1> face_quadrature_formula(fe_dgrt.degree + 1);
1007 * quadrature_formula,
1011 * face_quadrature_formula,
1017 * quadrature_formula,
1022 * face_quadrature_formula,
1029 *
const unsigned int dofs_per_cell_dgrt = fe_dgrt.n_dofs_per_cell();
1031 *
const unsigned int n_q_points = fe_values.get_quadrature().size();
1032 *
const unsigned int n_q_points_dgrt = fe_values_dgrt.get_quadrature().size();
1034 *
const unsigned int n_face_q_points = fe_face_values.get_quadrature().size();
1036 * RightHandSide<dim> right_hand_side;
1037 * std::vector<double> right_hand_side_values(n_q_points);
1039 *
const Coefficient<dim> coefficient;
1040 * std::vector<Tensor<2, dim>> coefficient_values(n_q_points);
1042 * std::vector<types::global_dof_index> local_dof_indices(
dofs_per_cell);
1047 * Next, let us declare the various cell matrices discussed in the
1061 * @p face component of the shape
functions.
1070 * This
finally gets us in position to
loop over all cells. On
1071 * each cell, we will
first calculate the various cell matrices
1072 * used to construct the local
matrix -- as they depend on the
1073 * cell in question, they need to be re-computed on each cell. We
1074 * need shape
functions for the Raviart-Thomas space as well,
for
1075 * which we need to create
first an iterator to the cell of the
1076 *
triangulation, which we can obtain by assignment from the cell
1080 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1082 * fe_values.
reinit(cell);
1086 * fe_values_dgrt.reinit(cell_dgrt);
1088 * right_hand_side.value_list(fe_values.get_quadrature_points(),
1089 * right_hand_side_values);
1090 * coefficient.value_list(fe_values.get_quadrature_points(),
1091 * coefficient_values);
1096 *
for the Raviart-Thomas space. Hence, we need to
loop over
1100 * cell_matrix_M = 0;
1101 *
for (
unsigned int q = 0; q < n_q_points_dgrt; ++q)
1102 *
for (
unsigned int i = 0; i < dofs_per_cell_dgrt; ++i)
1104 *
const Tensor<1, dim> v_i = fe_values_dgrt[velocities].value(i, q);
1105 *
for (
unsigned int k = 0; k < dofs_per_cell_dgrt; ++k)
1108 * fe_values_dgrt[velocities].value(k, q);
1109 * cell_matrix_M(i, k) += (v_i * v_k * fe_values_dgrt.JxW(q));
1114 * Next we take the inverse of
this matrix by
using
1116 * the coefficient
matrix @f$C^K@f$ later. It is worth recalling
1117 * later that `cell_matrix_M` actually contains the *inverse*
1118 * of @f$M^K@f$ after
this call.
1121 * cell_matrix_M.gauss_jordan();
1125 * From the introduction, we know that the right hand side
1126 * @f$G^K@f$ of the equation that defines @f$C^K@f$ is the difference
1127 * between a face integral and a cell integral. Here, we
1129 * interior. Each component of
this matrix is the integral of
1130 * a product between a basis
function of the polynomial space
1131 * and the divergence of a basis
function of the
1132 * Raviart-Thomas space. These basis
functions are defined in
1136 * cell_matrix_G = 0;
1137 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1138 *
for (
unsigned int i = 0; i < dofs_per_cell_dgrt; ++i)
1140 *
const double div_v_i =
1141 * fe_values_dgrt[velocities].divergence(i, q);
1144 *
const double phi_j_interior =
1145 * fe_values[pressure_interior].value(j, q);
1147 * cell_matrix_G(i, j) -=
1148 * (div_v_i * phi_j_interior * fe_values.JxW(q));
1156 * Each component is the integral of a product between a basis
function
1157 * of the polynomial space and the dot product of a basis
function of
1158 * the Raviart-Thomas space and the normal vector. So we
loop over all
1159 * the faces of the element and obtain the normal vector.
1162 *
for (
const auto &face : cell->face_iterators())
1164 * fe_face_values.reinit(cell, face);
1165 * fe_face_values_dgrt.reinit(cell_dgrt, face);
1167 *
for (
unsigned int q = 0; q < n_face_q_points; ++q)
1169 *
const Tensor<1, dim> &normal = fe_face_values.normal_vector(q);
1171 *
for (
unsigned int i = 0; i < dofs_per_cell_dgrt; ++i)
1174 * fe_face_values_dgrt[velocities].value(i, q);
1177 *
const double phi_j_face =
1178 * fe_face_values[pressure_face].value(j, q);
1180 * cell_matrix_G(i, j) +=
1181 * ((v_i * normal) * phi_j_face * fe_face_values.JxW(q));
1189 * @p cell_matrix_C is then the
matrix product between the
1191 * (where
this inverse is stored in @p cell_matrix_M):
1194 * cell_matrix_G.Tmmult(cell_matrix_C, cell_matrix_M);
1198 * Finally we can compute the local
matrix @f$A^K@f$. Element
1199 * @f$A^K_{ij}@f$ is given by @f$\int_{
E} \sum_{k,
l} C_{ik} C_{jl}
1200 * (\mathbf{K} \mathbf{v}_k) \cdot \mathbf{v}_l
1201 * \mathrm{
d}x@f$. We have calculated the coefficients @f$C@f$ in
1202 * the previous step, and so obtain the following after
1203 * suitably re-arranging the loops:
1207 *
for (
unsigned int q = 0; q < n_q_points_dgrt; ++q)
1209 *
for (
unsigned int k = 0; k < dofs_per_cell_dgrt; ++k)
1212 * fe_values_dgrt[velocities].value(k, q);
1213 *
for (
unsigned int l = 0;
l < dofs_per_cell_dgrt; ++
l)
1216 * fe_values_dgrt[velocities].value(
l, q);
1220 * local_matrix(i, j) +=
1221 * (coefficient_values[q] * cell_matrix_C[i][k] * v_k) *
1222 * cell_matrix_C[j][
l] * v_l * fe_values_dgrt.JxW(q);
1229 * Next, we calculate the right hand side, @f$\int_{K} f q \mathrm{
d}x@f$:
1233 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1236 * cell_rhs(i) += (fe_values[pressure_interior].value(i, q) *
1237 * right_hand_side_values[q] * fe_values.JxW(q));
1242 * The last step is to distribute components of the local
1243 *
matrix into the system
matrix and transfer components of
1244 * the cell right hand side into the system right hand side:
1247 * cell->get_dof_indices(local_dof_indices);
1248 * constraints.distribute_local_to_global(
1249 * local_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
1258 * <a name=
"WGDarcyEquationdimsolve"></a>
1259 * <h4>WGDarcyEquation<dim>::solve</h4>
1263 * This step is rather trivial and the same as in many previous
1264 * tutorial programs:
1267 *
template <
int dim>
1268 *
void WGDarcyEquation<dim>::solve()
1270 *
SolverControl solver_control(1000, 1
e-8 * system_rhs.l2_norm());
1273 * constraints.distribute(solution);
1280 * <a name=
"WGDarcyEquationdimcompute_postprocessed_velocity"></a>
1281 * <h4>WGDarcyEquation<dim>::compute_postprocessed_velocity</h4>
1285 * In
this function, compute the velocity field from the pressure
1286 * solution previously computed. The
1287 * velocity is defined as @f$\mathbf{u}_h = \mathbf{Q}_h \left(
1288 * -\mathbf{K}\nabla_{
w,
d}p_h \right)@f$, which requires us to compute
1289 * many of the same terms as in the assembly of the system
matrix.
1290 * There are also the matrices @f$E^K,D^K@f$ we need to
assemble (see
1291 * the introduction) but they really just follow the same kind of
1296 * Computing the same matrices here as we have already done in the
1297 * `assemble_system()`
function is of course wasteful in terms of
1298 * CPU time. Likewise, we
copy some of the code from there to
this
1299 *
function, and
this is also generally a poor idea.
A better
1300 * implementation might provide
for a
function that encapsulates
1301 *
this duplicated code. One could also think of
using the classic
1302 * trade-off between computing efficiency and memory efficiency to
1303 * only compute the @f$C^K@f$ matrices once per cell during the
1304 * assembly, storing them somewhere on the side, and re-
using them
1305 * here. (This is what @ref step_51
"step-51" does,
for example, where the
1306 * `assemble_system()`
function takes an argument that determines
1307 * whether the local matrices are recomputed, and a similar approach
1308 * -- maybe with storing local matrices elsewhere -- could be
1309 * adapted
for the current program.)
1312 *
template <
int dim>
1313 *
void WGDarcyEquation<dim>::compute_postprocessed_velocity()
1315 * darcy_velocity.reinit(dof_handler_dgrt.n_dofs());
1317 *
const QGauss<dim> quadrature_formula(fe_dgrt.degree + 1);
1318 *
const QGauss<dim - 1> face_quadrature_formula(fe_dgrt.degree + 1);
1321 * quadrature_formula,
1326 * face_quadrature_formula,
1332 * quadrature_formula,
1338 * face_quadrature_formula,
1344 *
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
1345 *
const unsigned int dofs_per_cell_dgrt = fe_dgrt.n_dofs_per_cell();
1347 *
const unsigned int n_q_points = fe_values.get_quadrature().size();
1348 *
const unsigned int n_q_points_dgrt = fe_values_dgrt.get_quadrature().size();
1350 *
const unsigned int n_face_q_points = fe_face_values.get_quadrature().size();
1353 * std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1354 * std::vector<types::global_dof_index> local_dof_indices_dgrt(
1355 * dofs_per_cell_dgrt);
1366 *
const Coefficient<dim> coefficient;
1367 * std::vector<Tensor<2, dim>> coefficient_values(n_q_points_dgrt);
1375 * In the introduction, we explained how to calculate the numerical velocity
1376 * on the cell. We need the pressure solution
values on each cell,
1377 * coefficients of the Gram
matrix and coefficients of the @f$L_2@f$ projection.
1378 * We have already calculated the global solution, so we will
extract the
1379 * cell solution from the global solution. The coefficients of the Gram
1380 *
matrix have been calculated when we assembled the system
matrix for the
1381 * pressures. We will
do the same way here. For the coefficients of the
1382 * projection, we
do matrix multiplication, i.e., the inverse of the Gram
1383 *
matrix times the
matrix with @f$(\mathbf{K} \mathbf{
w}, \mathbf{
w})@f$ as
1384 * components. Then, we multiply all these coefficients and
call them beta.
1385 * The numerical velocity is the product of beta and the basis
functions of
1386 * the Raviart-Thomas space.
1390 * cell = dof_handler.begin_active(),
1391 * endc = dof_handler.end(), cell_dgrt = dof_handler_dgrt.begin_active();
1392 *
for (; cell != endc; ++cell, ++cell_dgrt)
1394 * fe_values.reinit(cell);
1395 * fe_values_dgrt.reinit(cell_dgrt);
1397 * coefficient.value_list(fe_values_dgrt.get_quadrature_points(),
1398 * coefficient_values);
1402 * The component of this <code>cell_matrix_E</code> is the integral of
1403 * @f$(\mathbf{K} \mathbf{
w}, \mathbf{
w})@f$. <code>cell_matrix_M</code> is
1407 * cell_matrix_M = 0;
1408 * cell_matrix_E = 0;
1409 * for (
unsigned int q = 0; q < n_q_points_dgrt; ++q)
1410 *
for (
unsigned int i = 0; i < dofs_per_cell_dgrt; ++i)
1412 *
const Tensor<1, dim> v_i = fe_values_dgrt[velocities].value(i, q);
1413 *
for (
unsigned int k = 0; k < dofs_per_cell_dgrt; ++k)
1416 * fe_values_dgrt[velocities].value(k, q);
1418 * cell_matrix_E(i, k) +=
1419 * (coefficient_values[q] * v_i * v_k * fe_values_dgrt.JxW(q));
1421 * cell_matrix_M(i, k) += (v_i * v_k * fe_values_dgrt.JxW(q));
1427 * To compute the
matrix @f$D@f$ mentioned in the introduction, we
1428 * then need to evaluate @f$D=M^{-1}
E@f$ as explained in the
1432 * cell_matrix_M.gauss_jordan();
1433 * cell_matrix_M.mmult(cell_matrix_D, cell_matrix_E);
1437 * Then we also need, again, to compute the
matrix @f$C@f$ that is
1438 * used to evaluate the weak discrete
gradient. This is the
1439 * exact same code as used in the assembly of the system
1443 * cell_matrix_G = 0;
1444 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1445 *
for (
unsigned int i = 0; i < dofs_per_cell_dgrt; ++i)
1447 *
const double div_v_i =
1448 * fe_values_dgrt[velocities].divergence(i, q);
1449 *
for (
unsigned int j = 0; j < dofs_per_cell; ++j)
1451 *
const double phi_j_interior =
1452 * fe_values[pressure_interior].value(j, q);
1454 * cell_matrix_G(i, j) -=
1455 * (div_v_i * phi_j_interior * fe_values.JxW(q));
1459 *
for (
const auto &face : cell->face_iterators())
1461 * fe_face_values.reinit(cell, face);
1462 * fe_face_values_dgrt.reinit(cell_dgrt, face);
1464 *
for (
unsigned int q = 0; q < n_face_q_points; ++q)
1466 *
const Tensor<1, dim> &normal = fe_face_values.normal_vector(q);
1468 *
for (
unsigned int i = 0; i < dofs_per_cell_dgrt; ++i)
1471 * fe_face_values_dgrt[velocities].value(i, q);
1472 *
for (
unsigned int j = 0; j < dofs_per_cell; ++j)
1474 *
const double phi_j_face =
1475 * fe_face_values[pressure_face].value(j, q);
1477 * cell_matrix_G(i, j) +=
1478 * ((v_i * normal) * phi_j_face * fe_face_values.JxW(q));
1483 * cell_matrix_G.Tmmult(cell_matrix_C, cell_matrix_M);
1487 * Finally, we need to
extract the pressure unknowns that
1488 * correspond to the current cell:
1491 * cell->get_dof_values(solution, cell_solution);
1495 * We are now in a position to compute the local velocity
1496 * unknowns (with respect to the Raviart-Thomas space we are
1497 * projecting the term @f$-\mathbf K \nabla_{
w,
d} p_h@f$ into):
1500 * cell_velocity = 0;
1501 *
for (
unsigned int k = 0; k < dofs_per_cell_dgrt; ++k)
1502 *
for (
unsigned int j = 0; j < dofs_per_cell_dgrt; ++j)
1503 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1504 * cell_velocity(k) +=
1505 * -(cell_solution(i) * cell_matrix_C(i, j) * cell_matrix_D(k, j));
1509 * We compute Darcy velocity.
1510 * This is same as cell_velocity but used to graph Darcy velocity.
1513 * cell_dgrt->get_dof_indices(local_dof_indices_dgrt);
1514 *
for (
unsigned int k = 0; k < dofs_per_cell_dgrt; ++k)
1515 *
for (
unsigned int j = 0; j < dofs_per_cell_dgrt; ++j)
1516 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1517 * darcy_velocity(local_dof_indices_dgrt[k]) +=
1518 * -(cell_solution(i) * cell_matrix_C(i, j) * cell_matrix_D(k, j));
1527 * <a name=
"WGDarcyEquationdimcompute_pressure_error"></a>
1528 * <h4>WGDarcyEquation<dim>::compute_pressure_error</h4>
1532 * This part is to calculate the @f$L_2@f$ error of the pressure. We
1533 * define a vector that holds the
norm of the error on each cell.
1535 * error in the @f$L_2@f$
norm on each cell. However, we really only
1536 * care about the error in the interior component of the solution
1537 * vector (we can't even evaluate the interface pressures at the
1538 * quadrature points because these are all located in the interior
1539 * of cells) and consequently have to use a weight function that
1540 * ensures that the interface component of the solution variable is
1542 * arguments indicate which component we want to select (component
1543 *
zero, i.
e., the interior pressures) and how many components there
1544 * are in total (two).
1547 * template <
int dim>
1548 *
void WGDarcyEquation<dim>::compute_pressure_error()
1554 * ExactPressure<dim>(),
1555 * difference_per_cell,
1558 * &select_interior_pressure);
1560 *
const double L2_error = difference_per_cell.l2_norm();
1561 * std::cout <<
"L2_error_pressure " << L2_error << std::endl;
1569 * <a name=
"WGDarcyEquationdimcompute_velocity_error"></a>
1570 * <h4>WGDarcyEquation<dim>::compute_velocity_error</h4>
1574 * In
this function, we evaluate @f$L_2@f$ errors
for the velocity on
1575 * each cell, and @f$L_2@f$ errors
for the flux on faces. The
function
1576 * relies on the `compute_postprocessed_velocity()`
function having
1577 * previous computed, which computes the velocity field based on the
1578 * pressure solution that has previously been computed.
1582 * We are going to evaluate velocities on each cell and calculate
1583 * the difference between numerical and exact velocities.
1586 *
template <
int dim>
1587 *
void WGDarcyEquation<dim>::compute_velocity_errors()
1589 *
const QGauss<dim> quadrature_formula(fe_dgrt.degree + 1);
1590 *
const QGauss<dim - 1> face_quadrature_formula(fe_dgrt.degree + 1);
1593 * quadrature_formula,
1599 * face_quadrature_formula,
1605 *
const unsigned int n_q_points_dgrt = fe_values_dgrt.get_quadrature().size();
1606 *
const unsigned int n_face_q_points_dgrt =
1607 * fe_face_values_dgrt.get_quadrature().size();
1609 * std::vector<Tensor<1, dim>> velocity_values(n_q_points_dgrt);
1610 * std::vector<Tensor<1, dim>> velocity_face_values(n_face_q_points_dgrt);
1614 *
const ExactVelocity<dim> exact_velocity;
1616 *
double L2_err_velocity_cell_sqr_global = 0;
1617 *
double L2_err_flux_sqr = 0;
1621 * Having previously computed the postprocessed velocity, we here
1622 * only have to
extract the corresponding
values on each cell and
1623 * face and compare it to the exact
values.
1626 *
for (
const auto &cell_dgrt : dof_handler_dgrt.active_cell_iterators())
1628 * fe_values_dgrt.reinit(cell_dgrt);
1632 * First compute the @f$L_2@f$ error between the postprocessed velocity
1633 * field and the exact
one:
1636 * fe_values_dgrt[velocities].get_function_values(darcy_velocity,
1638 *
double L2_err_velocity_cell_sqr_local = 0;
1639 *
for (
unsigned int q = 0; q < n_q_points_dgrt; ++q)
1643 * exact_velocity.value(fe_values_dgrt.quadrature_point(q));
1645 * L2_err_velocity_cell_sqr_local +=
1646 * ((velocity - true_velocity) * (velocity - true_velocity) *
1647 * fe_values_dgrt.JxW(q));
1649 * L2_err_velocity_cell_sqr_global += L2_err_velocity_cell_sqr_local;
1653 * For reconstructing the flux we need the size of cells and
1654 * faces. Since fluxes are calculated on faces, we have the
1655 *
loop over all four faces of each cell. To calculate the
1656 * face velocity, we
extract values at the quadrature points from the
1657 * `darcy_velocity` which we have computed previously. Then, we
1658 * calculate the squared velocity error in normal direction. Finally, we
1659 * calculate the @f$L_2@f$ flux error on the cell by appropriately scaling
1660 * with face and cell areas and add it to the global error.
1663 *
const double cell_area = cell_dgrt->measure();
1664 *
for (
const auto &face_dgrt : cell_dgrt->face_iterators())
1666 *
const double face_length = face_dgrt->measure();
1667 * fe_face_values_dgrt.reinit(cell_dgrt, face_dgrt);
1668 * fe_face_values_dgrt[velocities].get_function_values(
1669 * darcy_velocity, velocity_face_values);
1671 *
double L2_err_flux_face_sqr_local = 0;
1672 *
for (
unsigned int q = 0; q < n_face_q_points_dgrt; ++q)
1676 * exact_velocity.value(fe_face_values_dgrt.quadrature_point(q));
1679 * fe_face_values_dgrt.normal_vector(q);
1681 * L2_err_flux_face_sqr_local +=
1682 * ((velocity * normal - true_velocity * normal) *
1683 * (velocity * normal - true_velocity * normal) *
1684 * fe_face_values_dgrt.JxW(q));
1686 *
const double err_flux_each_face =
1687 * L2_err_flux_face_sqr_local / face_length * cell_area;
1688 * L2_err_flux_sqr += err_flux_each_face;
1694 * After adding up errors over all cells and faces, we take the
1695 * square root and get the @f$L_2@f$ errors of velocity and
1696 * flux. These we output to screen.
1699 *
const double L2_err_velocity_cell =
1700 *
std::sqrt(L2_err_velocity_cell_sqr_global);
1701 *
const double L2_err_flux_face =
std::sqrt(L2_err_flux_sqr);
1703 * std::cout <<
"L2_error_vel: " << L2_err_velocity_cell << std::endl
1704 * <<
"L2_error_flux: " << L2_err_flux_face << std::endl;
1711 * <a name=
"WGDarcyEquationoutput_results"></a>
1712 * <h4>WGDarcyEquation::output_results</h4>
1716 * We have two sets of results to output: the interior solution and
1717 * the skeleton solution. We use <code>
DataOut</code> to visualize
1718 * interior results. The graphical output
for the skeleton results
1723 * In both of the output files, both the interior and the face
1724 * variables are stored. For the
interface output, the output file
1725 * simply contains the interpolation of the interior pressures onto
1726 * the faces, but because it is undefined which of the two interior
1727 * pressure variables you get from the two adjacent cells, it is
1728 * best to ignore the interior pressure in the
interface output
1729 * file. Conversely,
for the cell interior output file, it is of
1730 * course impossible to show any
interface pressures @f$p^\partial@f$,
1731 * because these are only available on interfaces and not cell
1732 * interiors. Consequently, you will see them shown as an
invalid
1733 *
value (such as an infinity).
1737 * For the cell interior output, we also want to output the velocity
1738 * variables. This is a bit tricky since it lives on the same mesh
1739 * but uses a different
DoFHandler object (the pressure variables live
1740 * on the `dof_handler`
object, the Darcy velocity on the `dof_handler_dgrt`
1741 *
object). Fortunately, there are variations of the
1743 *
DoFHandler a vector corresponds to, and consequently we can visualize
1744 * the data from both
DoFHandler objects within the same file.
1747 * template <
int dim>
1748 *
void WGDarcyEquation<dim>::output_results() const
1755 * First attach the pressure solution to the
DataOut object:
1758 *
const std::vector<std::string> solution_names = {
"interior_pressure",
1759 *
"interface_pressure"};
1764 * Then
do the same with the Darcy velocity field, and
continue
1765 * with writing everything out into a file.
1768 *
const std::vector<std::string> velocity_names(dim,
"velocity");
1769 *
const std::vector<
1771 * velocity_component_interpretation(
1776 * velocity_component_interpretation);
1779 * std::ofstream output(
"solution_interior.vtu");
1785 * data_out_faces.attach_dof_handler(dof_handler);
1786 * data_out_faces.add_data_vector(solution,
"Pressure_Face");
1787 * data_out_faces.build_patches(fe.degree);
1788 * std::ofstream face_output(
"solution_interface.vtu");
1789 * data_out_faces.write_vtu(face_output);
1797 * <a name=
"WGDarcyEquationrun"></a>
1802 * This is the
final function of the main
class. It calls the other
functions
1806 *
template <
int dim>
1809 * std::cout <<
"Solving problem in " << dim <<
" space dimensions."
1813 * assemble_system();
1815 * compute_postprocessed_velocity();
1816 * compute_pressure_error();
1817 * compute_velocity_errors();
1827 * <a name=
"Thecodemaincodefunction"></a>
1828 * <h3>The <code>main</code>
function</h3>
1832 * This is the main
function. We can change the dimension here to
run in 3
d.
1839 * Step61::WGDarcyEquation<2> wg_darcy(0);
1842 *
catch (std::exception &exc)
1844 * std::cerr << std::endl
1846 * <<
"----------------------------------------------------"
1848 * std::cerr <<
"Exception on processing: " << std::endl
1849 * << exc.what() << std::endl
1850 * <<
"Aborting!" << std::endl
1851 * <<
"----------------------------------------------------"
1857 * std::cerr << std::endl
1859 * <<
"----------------------------------------------------"
1861 * std::cerr <<
"Unknown exception!" << std::endl
1862 * <<
"Aborting!" << std::endl
1863 * <<
"----------------------------------------------------"
1871 <a name=
"Results"></a><h1>Results</h1>
1874 We
run the program with a right hand side that will produce the
1875 solution @f$p =
\sin(\pi x)
\sin(\pi y)@f$ and with homogeneous Dirichlet
1876 boundary conditions in the domain @f$\Omega = (0,1)^2@f$. In addition, we
1877 choose the coefficient
matrix in the differential
operator
1878 @f$\mathbf{K}@f$ as the
identity matrix. We test
this setup
using
1879 @f$\mbox{WG}(Q_0,Q_0;RT_{[0]})@f$, @f$\mbox{WG}(Q_1,Q_1;RT_{[1]})@f$ and
1880 @f$\mbox{WG}(Q_2,Q_2;RT_{[2]})@f$ element combinations, which
one can
1881 select by
using the appropriate constructor argument
for the
1882 `WGDarcyEquation`
object in `main()`. We will then visualize pressure
1883 values in interiors of cells and on faces. We want to see that the
1884 pressure maximum is around 1 and the minimum is around 0. With mesh
1885 refinement, the convergence rates of pressure, velocity and flux
1886 should then be around 1
for @f$\mbox{WG}(Q_0,Q_0;RT_{[0]})@f$ , 2
for
1887 @f$\mbox{WG}(Q_1,Q_1;RT_{[1]})@f$, and 3
for
1888 @f$\mbox{WG}(Q_2,Q_2;RT_{[2]})@f$.
1891 <a name=
"TestresultsoniWGQsub0subQsub0subRTsub0subi"></a><h3>Test results on <i>WG(Q<sub>0</sub>,Q<sub>0</sub>;RT<sub>[0]</sub>)</i></h3>
1894 The following figures show interior pressures and face pressures
using the
1895 @f$\mbox{WG}(Q_0,Q_0;RT_{[0]})@f$ element. The mesh is refined 2 times (top)
1896 and 4 times (bottom), respectively. (This number can be adjusted in the
1897 `make_grid()`
function.) When the mesh is coarse,
one can see
1898 the face pressures @f$p^\partial@f$ neatly between the
values of the interior
1899 pressures @f$p^\circ@f$ on the two adjacent cells.
1901 <table align=
"center">
1903 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg000_2d_2.png" alt=
""></td>
1904 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg000_3d_2.png" alt=
""></td>
1907 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg000_2d_4.png" alt=
""></td>
1908 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg000_3d_4.png" alt=
""></td>
1912 From the figures, we can see that with the mesh refinement, the maximum and
1913 minimum pressure
values are approaching the
values we expect.
1914 Since the mesh is a rectangular mesh and
numbers of cells in each direction is even, we
1915 have
symmetric solutions. From the 3
d figures on the right,
1916 we can see that on @f$\mbox{WG}(Q_0,Q_0;RT_{[0]})@f$, the pressure is a constant
1917 in the interior of the cell, as expected.
1919 <a name=
"Convergencetableforik0i"></a><h4>Convergence table for <i>k=0</i></h4>
1922 We
run the code with differently refined meshes (chosen in the `make_grid()`
function)
1923 and get the following convergence rates of pressure,
1924 velocity, and flux (as defined in the introduction).
1926 <table align=
"center" class=
"doxtable">
1928 <th>number of refinements </th><th> @f$\|p-p_h^\circ\|@f$ </th><th> @f$\|\mathbf{u}-\mathbf{u}_h\|@f$ </th><th> @f$\|(\mathbf{u}-\mathbf{u}_h) \cdot \mathbf{n}\|@f$ </th>
1931 <td> 2 </td><td> 1.587e-01 </td><td> 5.113e-01 </td><td> 7.062e-01 </td>
1934 <td> 3 </td><td> 8.000e-02 </td><td> 2.529e-01 </td><td> 3.554e-01 </td>
1937 <td> 4 </td><td> 4.006e-02 </td><td> 1.260e-01 </td><td> 1.780e-01 </td>
1940 <td> 5 </td><td> 2.004e-02 </td><td> 6.297e-02 </td><td> 8.902e-02 </td>
1943 <th>Conv.rate </th><th> 1.00 </th><th> 1.00 </th><th> 1.00 </th>
1947 We can see that the convergence rates of @f$\mbox{WG}(Q_0,Q_0;RT_{[0]})@f$ are around 1.
1948 This, of course, matches our theoretical expectations.
1951 <a name=
"TestresultsoniWGQsub1subQsub1subRTsub1subi"></a><h3>Test results on <i>WG(Q<sub>1</sub>,Q<sub>1</sub>;RT<sub>[1]</sub>)</i></h3>
1954 We can repeat the experiment from above
using the next higher polynomial
1956 The following figures are interior pressures and face pressures implemented
using
1957 @f$\mbox{WG}(Q_1,Q_1;RT_{[1]})@f$. The mesh is refined 4 times. Compared to the
1958 previous figures
using
1959 @f$\mbox{WG}(Q_0,Q_0;RT_{[0]})@f$, on each cell, the solution is no longer constant
1960 on each cell, as we now use bilinear polynomials to
do the approximation.
1961 Consequently, there are 4 pressure
values in
one interior, 2 pressure
values on
1964 <table align=
"center">
1966 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg111_2d_4.png" alt=
""></td>
1967 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg111_3d_4.png" alt=
""></td>
1971 Compared to the corresponding image
for the @f$\mbox{WG}(Q_0,Q_0;RT_{[0]})@f$
1972 combination, the solution is now substantially more accurate and, in
1973 particular so close to being continuous at the interfaces that we can
1974 no longer distinguish the interface pressures @f$p^\partial@f$ from the
1975 interior pressures @f$p^\circ@f$ on the adjacent cells.
1977 <a name=
"Convergencetableforik1i"></a><h4>Convergence table for <i>k=1</i></h4>
1980 The following are the convergence rates of pressure, velocity, and flux
1981 we obtain from
using the @f$\mbox{WG}(Q_1,Q_1;RT_{[1]})@f$ element combination:
1983 <table align=
"center" class=
"doxtable">
1985 <th>number of refinements </th><th> @f$\|p-p_h^\circ\|@f$ </th><th> @f$\|\mathbf{u}-\mathbf{u}_h\|@f$ </th><th> @f$\|(\mathbf{u}-\mathbf{u}_h) \cdot \mathbf{n}\|@f$ </th>
1988 <td> 2 </td><td> 1.613e-02 </td><td> 5.093e-02 </td><td> 7.167e-02 </td>
1991 <td> 3 </td><td> 4.056e-03 </td><td> 1.276e-02 </td><td> 1.802e-02 </td>
1994 <td> 4 </td><td> 1.015e-03 </td><td> 3.191e-03 </td><td> 4.512e-03 </td>
1997 <td> 5 </td><td> 2.540e-04 </td><td> 7.979e-04 </td><td> 1.128e-03 </td>
2000 <th>Conv.rate </th><th> 2.00 </th><th> 2.00 </th><th> 2.00 </th>
2004 The convergence rates of @f$WG(Q_1,Q_1;RT_{[1]})@f$ are around 2, as expected.
2008 <a name=
"TestresultsoniWGQsub2subQsub2subRTsub2subi"></a><h3>Test results on <i>WG(Q<sub>2</sub>,Q<sub>2</sub>;RT<sub>[2]</sub>)</i></h3>
2011 Let us go
one polynomial degree higher.
2012 The following are interior pressures and face pressures implemented
using
2013 @f$WG(Q_2,Q_2;RT_{[2]})@f$, with mesh size @f$h = 1/32@f$ (i.e., 5 global mesh
2014 refinement steps). In the program, we use
2015 `data_out_face.build_patches(fe.degree)` when generating graphical output
2017 we divide each 2
d cell interior into 4 subcells in order to provide a better
2018 visualization of the quadratic polynomials.
2019 <table align=
"center">
2021 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg222_2d_5.png" alt=
""></td>
2022 <td><img src=
"https://www.dealii.org/images/steps/developer/step-61.wg222_3d_5.png" alt=
""></td>
2027 <a name=
"Convergencetableforik2i"></a><h4>Convergence table for <i>k=2</i></h4>
2030 As before, we can generate convergence data
for the
2031 @f$L_2@f$ errors of pressure, velocity, and flux
2032 using the @f$\mbox{WG}(Q_2,Q_2;RT_{[2]})@f$ combination:
2034 <table align=
"center" class=
"doxtable">
2036 <th>number of refinements </th><th> @f$\|p-p_h^\circ\|@f$ </th><th> @f$\|\mathbf{u}-\mathbf{u}_h\|@f$ </th><th> @f$\|(\mathbf{u}-\mathbf{u}_h) \cdot \mathbf{n}\|@f$ </th>
2039 <td> 2 </td><td> 1.072e-03 </td><td> 3.375e-03 </td><td> 4.762e-03 </td>
2042 <td> 3 </td><td> 1.347e-04 </td><td> 4.233e-04 </td><td> 5.982e-04 </td>
2045 <td> 4 </td><td> 1.685e-05 </td><td> 5.295e-05 </td><td> 7.487e-05 </td>
2048 <td> 5 </td><td> 2.107e-06 </td><td> 6.620e-06 </td><td> 9.362e-06 </td>
2051 <th>Conv.rate </th><th> 3.00 </th><th> 3.00 </th><th> 3.00 </th>
2055 Once more, the convergence rates of @f$\mbox{WG}(Q_2,Q_2;RT_{[2]})@f$ is
2056 as expected, with
values around 3.
2059 <a name=
"PlainProg"></a>
2060 <h1> The plain program</h1>
2061 @include
"step-61.cc"
std::vector< bool > component_mask
void add_data_vector(const VectorType &data, const std::vector< std::string > &names, const DataVectorType type=type_automatic, const std::vector< DataComponentInterpretation::DataComponentInterpretation > &data_component_interpretation={})
virtual void build_patches(const unsigned int n_subdivisions=0)
void reinit(const Triangulation< dim, spacedim > &tria)
const unsigned int dofs_per_cell
const SmartPointer< const FiniteElement< dim, spacedim >, FEValuesBase< dim, spacedim > > fe
void reinit(const TriaIterator< DoFCellAccessor< dim, spacedim, level_dof_access >> &cell)
const Quadrature< dim > quadrature
virtual RangeNumberType value(const Point< dim > &p, const unsigned int component=0) const
virtual value_type value(const Point< dim > &p) const
VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &x)
@ 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 write_vtu(std::ostream &out) const
typename ActiveSelector::active_cell_iterator active_cell_iterator
void loop(ITERATOR begin, typename identity< ITERATOR >::type 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, SparsityPatternType &sparsity_pattern, const AffineConstraints< number > &constraints=AffineConstraints< number >(), const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
DataComponentInterpretation
@ component_is_part_of_vector
void approximate(SynchronousIterators< std::tuple< typename DoFHandler< dim, spacedim >::active_cell_iterator, Vector< float >::iterator >> const &cell, const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof_handler, const InputVector &solution, const unsigned int component)
void hyper_cube(Triangulation< dim, spacedim > &tria, const double left=0., const double right=1., const bool colorize=false)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
static const types::blas_int zero
@ matrix
Contents is actually a matrix.
@ symmetric
Matrix is symmetric.
static const types::blas_int one
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim >>> &Du)
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)
Tensor< 2, dim, Number > w(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
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)
constexpr ReturnType< rank, T >::value_type & extract(T &t, const ArrayType &indices)
void call(const std::function< RT()> &function, internal::return_value< RT > &ret_val)
void run(const Iterator &begin, const typename identity< Iterator >::type &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)
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)
static constexpr double E
static constexpr double PI
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation