528 *
double return_value = 0.0;
532 * return_value = 24.0 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1])) +
533 * +24.0 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0])) +
534 * 2.0 * (2.0 - 12.0 * p[0] + 12.0 * p[0] * p[0]) *
535 * (2.0 - 12.0 * p[1] + 12.0 * p[1] * p[1]);
539 * return_value = 24.0 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1]) *
540 * p[2] * (1.0 - p[2])) +
541 * 24.0 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]) *
542 * p[2] * (1.0 - p[2])) +
543 * 24.0 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]) *
544 * p[1] * (1.0 - p[1])) +
545 * 2.0 * (2.0 - 12.0 * p[0] + 12.0 * p[0] * p[0]) *
546 * (2.0 - 12.0 * p[1] + 12.0 * p[1] * p[1]) *
547 * Utilities::fixed_power<2>(p[2] * (1.0 - p[2])) +
548 * 2.0 * (2.0 - 12.0 * p[0] + 12.0 * p[0] * p[0]) *
549 * (2.0 - 12.0 * p[2] + 12.0 * p[2] * p[2]) *
550 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1])) +
551 * 2.0 * (2.0 - 12.0 * p[1] + 12.0 * p[1] * p[1]) *
552 * (2.0 - 12.0 * p[2] + 12.0 * p[2] * p[2]) *
553 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]));
558 *
return return_value;
565 * This
class implement the manufactured (exact) solution @f$u@f$. To compute the
566 * errors, we need the
value of @f$u@f$ as well as its
gradient and its Hessian.
570 *
class ExactSolution :
public Function<dim>
578 *
const unsigned int component = 0)
const override;
582 *
const unsigned int component = 0)
const override;
586 *
const unsigned int component = 0)
const override;
592 *
double ExactSolution<dim>::value(
const Point<dim> &p,
593 *
const unsigned int )
const
595 *
double return_value = 0.0;
600 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]) * p[1] * (1.0 - p[1]));
604 * return_value = Utilities::fixed_power<2>(
605 * p[0] * (1.0 - p[0]) * p[1] * (1.0 - p[1]) * p[2] * (1.0 - p[2]));
610 *
return return_value;
617 * ExactSolution<dim>::gradient(
const Point<dim> &p,
618 *
const unsigned int )
const
624 * return_gradient[0] =
625 * (2.0 * p[0] - 6.0 * Utilities::fixed_power<2>(p[0]) +
626 * 4.0 * Utilities::fixed_power<3>(p[0])) *
627 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1]));
628 * return_gradient[1] =
629 * (2.0 * p[1] - 6.0 * Utilities::fixed_power<2>(p[1]) +
630 * 4.0 * Utilities::fixed_power<3>(p[1])) *
631 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]));
635 * return_gradient[0] =
636 * (2.0 * p[0] - 6.0 * Utilities::fixed_power<2>(p[0]) +
637 * 4.0 * Utilities::fixed_power<3>(p[0])) *
638 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1]) * p[2] * (1.0 - p[2]));
639 * return_gradient[1] =
640 * (2.0 * p[1] - 6.0 * Utilities::fixed_power<2>(p[1]) +
641 * 4.0 * Utilities::fixed_power<3>(p[1])) *
642 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]) * p[2] * (1.0 - p[2]));
643 * return_gradient[2] =
644 * (2.0 * p[2] - 6.0 * Utilities::fixed_power<2>(p[2]) +
645 * 4.0 * Utilities::fixed_power<3>(p[2])) *
646 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]) * p[1] * (1.0 - p[1]));
651 *
return return_gradient;
658 * ExactSolution<dim>::hessian(
const Point<dim> &p,
659 *
const unsigned int )
const
665 * return_hessian[0][0] = (2.0 - 12.0 * p[0] + 12.0 * p[0] * p[0]) *
666 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1]));
667 * return_hessian[0][1] =
668 * (2.0 * p[0] - 6.0 * Utilities::fixed_power<2>(p[0]) +
669 * 4.0 * Utilities::fixed_power<3>(p[0])) *
670 * (2.0 * p[1] - 6.0 * Utilities::fixed_power<2>(p[1]) +
671 * 4.0 * Utilities::fixed_power<3>(p[1]));
672 * return_hessian[1][1] = (2.0 - 12.0 * p[1] + 12.0 * p[1] * p[1]) *
673 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]));
677 * return_hessian[0][0] =
678 * (2.0 - 12.0 * p[0] + 12.0 * p[0] * p[0]) *
679 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1]) * p[2] * (1.0 - p[2]));
680 * return_hessian[0][1] =
681 * (2.0 * p[0] - 6.0 * Utilities::fixed_power<2>(p[0]) +
682 * 4.0 * Utilities::fixed_power<3>(p[0])) *
683 * (2.0 * p[1] - 6.0 * Utilities::fixed_power<2>(p[1]) +
684 * 4.0 * Utilities::fixed_power<3>(p[1])) *
685 * Utilities::fixed_power<2>(p[2] * (1.0 - p[2]));
686 * return_hessian[0][2] =
687 * (2.0 * p[0] - 6.0 * Utilities::fixed_power<2>(p[0]) +
688 * 4.0 * Utilities::fixed_power<3>(p[0])) *
689 * (2.0 * p[2] - 6.0 * Utilities::fixed_power<2>(p[2]) +
690 * 4.0 * Utilities::fixed_power<3>(p[2])) *
691 * Utilities::fixed_power<2>(p[1] * (1.0 - p[1]));
692 * return_hessian[1][1] =
693 * (2.0 - 12.0 * p[1] + 12.0 * p[1] * p[1]) *
694 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]) * p[2] * (1.0 - p[2]));
695 * return_hessian[1][2] =
696 * (2.0 * p[1] - 6.0 * Utilities::fixed_power<2>(p[1]) +
697 * 4.0 * Utilities::fixed_power<3>(p[1])) *
698 * (2.0 * p[2] - 6.0 * Utilities::fixed_power<2>(p[2]) +
699 * 4.0 * Utilities::fixed_power<3>(p[2])) *
700 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]));
701 * return_hessian[2][2] =
702 * (2.0 - 12.0 * p[2] + 12.0 * p[2] * p[2]) *
703 * Utilities::fixed_power<2>(p[0] * (1.0 - p[0]) * p[1] * (1.0 - p[1]));
708 *
return return_hessian;
716 * <a name=
"step_82-ImplementationofthecodeBiLaplacianLDGLiftcodeclass"></a>
717 * <h3>Implementation of the <code>BiLaplacianLDGLift</code>
class</h3>
722 * <a name=
"step_82-BiLaplacianLDGLiftBiLaplacianLDGLift"></a>
723 * <h4>BiLaplacianLDGLift::BiLaplacianLDGLift</h4>
727 * In the constructor, we set the polynomial degree of the two finite element
728 * spaces, we associate the corresponding DoF handlers to the
triangulation,
729 * and we set the two penalty coefficients.
733 * BiLaplacianLDGLift<dim>::BiLaplacianLDGLift(
const unsigned int n_refinements,
734 *
const unsigned int fe_degree,
735 *
const double penalty_jump_grad,
736 *
const double penalty_jump_val)
737 * : n_refinements(n_refinements)
741 * , penalty_jump_grad(penalty_jump_grad)
742 * , penalty_jump_val(penalty_jump_val)
750 * <a name=
"step_82-BiLaplacianLDGLiftmake_grid"></a>
751 * <h4>BiLaplacianLDGLift::make_grid</h4>
755 * To build a mesh
for @f$\Omega=(0,1)^d@f$, we simply call the function
756 * <code>GridGenerator::hyper_cube</code> and then refine it
using
757 * <code>refine_global</code>. The number of refinements is hard-coded
762 *
void BiLaplacianLDGLift<dim>::make_grid()
764 * std::cout <<
"Building the mesh............." << std::endl;
770 * std::cout <<
"Number of active cells: " <<
triangulation.n_active_cells()
779 * <a name=
"step_82-BiLaplacianLDGLiftsetup_system"></a>
780 * <h4>BiLaplacianLDGLift::setup_system</h4>
784 * In the following function, we set up the degrees of freedom, the sparsity
785 * pattern, the
size of the
matrix @f$A@f$, and the
size of the solution and
786 * right-hand side vectors
787 * @f$\boldsymbol{U}@f$ and @f$\boldsymbol{
F}@f$. For the sparsity pattern, we cannot
789 * (as we would
do for instance
for the SIPG method) because we need to take
790 * into account the interactions of a neighboring cell with another
791 * neighboring cell as described in the introduction. The extended sparsity
792 * pattern is built by iterating over all the active cells. For the current
793 * cell, we collect all its degrees of freedom as well as the degrees of
794 * freedom of all its neighboring cells, and then couple everything with
799 *
void BiLaplacianLDGLift<dim>::setup_system()
801 * dof_handler.distribute_dofs(fe);
803 * std::cout <<
"Number of degrees of freedom: " << dof_handler.n_dofs()
808 *
const auto dofs_per_cell = fe.dofs_per_cell;
810 *
for (
const auto &cell : dof_handler.active_cell_iterators())
813 * cell->get_dof_indices(dofs);
815 *
for (
unsigned int f = 0; f < cell->n_faces(); ++f)
816 *
if (!cell->face(f)->at_boundary())
818 *
const auto neighbor_cell = cell->neighbor(f);
820 * std::vector<types::global_dof_index> tmp(dofs_per_cell);
821 * neighbor_cell->get_dof_indices(tmp);
823 * dofs.insert(std::end(dofs), std::begin(tmp), std::end(tmp));
826 *
for (
const auto i : dofs)
827 * for (const auto j : dofs)
834 * sparsity_pattern.copy_from(dsp);
837 *
matrix.reinit(sparsity_pattern);
838 * rhs.reinit(dof_handler.n_dofs());
840 * solution.reinit(dof_handler.n_dofs());
844 * At the
end of the function, we output
this sparsity pattern as
845 * a scalable vector graphic. You can visualize it by loading
this
846 * file in most web browsers:
849 * std::ofstream out(
"sparsity-pattern.svg");
850 * sparsity_pattern.print_svg(out);
858 * <a name=
"step_82-BiLaplacianLDGLiftassemble_system"></a>
859 * <h4>BiLaplacianLDGLift::assemble_system</h4>
863 * This function simply calls the two
functions responsible
864 *
for the assembly of the
matrix and the right-hand side.
868 *
void BiLaplacianLDGLift<dim>::assemble_system()
870 * std::cout <<
"Assembling the system............." << std::endl;
875 * std::cout <<
"Done. " << std::endl;
883 * <a name=
"step_82-BiLaplacianLDGLiftassemble_matrix"></a>
884 * <h4>BiLaplacianLDGLift::assemble_matrix</h4>
888 * This function assembles the
matrix @f$A@f$ whose entries are defined
889 * by @f$A_{ij}=A_h(\varphi_j,\varphi_i)@f$ which involves the product of
890 * discrete Hessians and the penalty terms.
894 *
void BiLaplacianLDGLift<dim>::assemble_matrix()
899 *
const QGauss<dim - 1> quad_face(fe.degree + 1);
901 *
const unsigned int n_q_points = quad.size();
902 *
const unsigned int n_q_points_face = quad_face.size();
912 *
const unsigned int n_dofs = fe_values.dofs_per_cell;
914 * std::vector<types::global_dof_index> local_dof_indices(n_dofs);
915 * std::vector<types::global_dof_index> local_dof_indices_neighbor(n_dofs);
916 * std::vector<types::global_dof_index> local_dof_indices_neighbor_2(n_dofs);
920 * As indicated in the introduction, the following matrices are used
for
921 * the contributions of the products of the discrete Hessians.
939 * The following matrices are used
for the contributions of the two
951 * std::vector<std::vector<Tensor<2, dim>>> discrete_hessians(
953 * std::vector<std::vector<std::vector<Tensor<2, dim>>>>
955 * discrete_hessians);
957 *
for (
const auto &cell : dof_handler.active_cell_iterators())
960 * cell->get_dof_indices(local_dof_indices);
964 * We now compute all the discrete Hessians that are not vanishing
965 * on the current cell, i.e., the discrete Hessian of all the basis
966 *
functions with support on the current cell or on one of its
970 * compute_discrete_hessians(cell,
972 * discrete_hessians_neigh);
976 * First, we compute and add the interactions of the degrees of freedom
977 * of the current cell.
980 * stiffness_matrix_cc = 0;
981 *
for (
unsigned int q = 0; q < n_q_points; ++q)
983 *
const double dx = fe_values.JxW(q);
985 *
for (
unsigned int i = 0; i < n_dofs; ++i)
986 *
for (
unsigned int j = 0; j < n_dofs; ++j)
991 * stiffness_matrix_cc(i, j) += scalar_product(H_j, H_i) *
dx;
995 *
for (
unsigned int i = 0; i < n_dofs; ++i)
996 *
for (
unsigned int j = 0; j < n_dofs; ++j)
998 *
matrix(local_dof_indices[i], local_dof_indices[j]) +=
999 * stiffness_matrix_cc(i, j);
1004 * Next, we compute and add the interactions of the degrees of freedom
1005 * of the current cell with those of its neighbors. Note that the
1006 * interactions of the degrees of freedom of a neighbor with those of
1007 * the same neighbor are included here.
1010 *
for (
unsigned int face_no = 0; face_no < cell->n_faces(); ++face_no)
1013 * cell->face(face_no);
1015 *
const bool at_boundary = face->at_boundary();
1020 * There is
nothing to be done
if boundary face (the liftings of
1021 * the Dirichlet BCs are accounted
for in the assembly of the
1022 * RHS; in fact,
nothing to be done in
this program since we
1023 * prescribe homogeneous BCs).
1030 * neighbor_cell = cell->neighbor(face_no);
1031 * neighbor_cell->get_dof_indices(local_dof_indices_neighbor);
1033 * stiffness_matrix_cn = 0;
1034 * stiffness_matrix_nc = 0;
1035 * stiffness_matrix_nn = 0;
1036 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1038 *
const double dx = fe_values.JxW(q);
1040 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1042 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1048 * discrete_hessians_neigh[face_no][i][q];
1050 * discrete_hessians_neigh[face_no][j][q];
1052 * stiffness_matrix_cn(i, j) +=
1053 * scalar_product(H_j_neigh, H_i) *
dx;
1054 * stiffness_matrix_nc(i, j) +=
1055 * scalar_product(H_j, H_i_neigh) *
dx;
1056 * stiffness_matrix_nn(i, j) +=
1057 * scalar_product(H_j_neigh, H_i_neigh) *
dx;
1062 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1064 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1066 *
matrix(local_dof_indices[i],
1067 * local_dof_indices_neighbor[j]) +=
1068 * stiffness_matrix_cn(i, j);
1069 *
matrix(local_dof_indices_neighbor[i],
1070 * local_dof_indices[j]) +=
1071 * stiffness_matrix_nc(i, j);
1072 *
matrix(local_dof_indices_neighbor[i],
1073 * local_dof_indices_neighbor[j]) +=
1074 * stiffness_matrix_nn(i, j);
1083 * We now compute and add the interactions of the degrees of freedom of
1084 * a neighboring cells with those of another neighboring cell (
this is
1085 * where we need the extended sparsity pattern).
1088 *
for (
unsigned int face_no = 0; face_no < cell->n_faces() - 1; ++face_no)
1091 * cell->face(face_no);
1093 *
const bool at_boundary = face->at_boundary();
1098 * Dirichlet BCs are accounted
for in the assembly of the RHS;
1099 * in fact,
nothing to be done in
this program since we
1100 * prescribe homogeneous BCs)
1109 *
for (
unsigned int face_no_2 = face_no + 1;
1110 * face_no_2 < cell->n_faces();
1114 * cell->face(face_no_2);
1116 *
const bool at_boundary_2 = face_2->at_boundary();
1117 *
if (!at_boundary_2)
1120 * neighbor_cell = cell->neighbor(face_no);
1121 * neighbor_cell->get_dof_indices(
1122 * local_dof_indices_neighbor);
1124 * neighbor_cell_2 = cell->neighbor(face_no_2);
1125 * neighbor_cell_2->get_dof_indices(
1126 * local_dof_indices_neighbor_2);
1128 * stiffness_matrix_n1n2 = 0;
1129 * stiffness_matrix_n2n1 = 0;
1131 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1133 *
const double dx = fe_values.JxW(q);
1135 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1136 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1139 * discrete_hessians_neigh[face_no][i][q];
1141 * discrete_hessians_neigh[face_no][j][q];
1144 * discrete_hessians_neigh[face_no_2][i][q];
1146 * discrete_hessians_neigh[face_no_2][j][q];
1148 * stiffness_matrix_n1n2(i, j) +=
1149 * scalar_product(H_j_neigh2, H_i_neigh) *
dx;
1150 * stiffness_matrix_n2n1(i, j) +=
1151 * scalar_product(H_j_neigh, H_i_neigh2) *
dx;
1155 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1156 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1158 *
matrix(local_dof_indices_neighbor[i],
1159 * local_dof_indices_neighbor_2[j]) +=
1160 * stiffness_matrix_n1n2(i, j);
1161 *
matrix(local_dof_indices_neighbor_2[i],
1162 * local_dof_indices_neighbor[j]) +=
1163 * stiffness_matrix_n2n1(i, j);
1173 * Finally, we compute and add the two penalty terms.
1176 *
for (
unsigned int face_no = 0; face_no < cell->n_faces(); ++face_no)
1179 * cell->face(face_no);
1181 *
const double mesh_inv = 1.0 / face->diameter();
1182 *
const double mesh3_inv =
1183 * 1.0 / Utilities::fixed_power<3>(face->diameter());
1185 * fe_face.reinit(cell, face_no);
1189 *
const bool at_boundary = face->at_boundary();
1192 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1194 *
const double dx = fe_face.JxW(q);
1196 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1197 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1199 * ip_matrix_cc(i, j) += penalty_jump_grad * mesh_inv *
1200 * fe_face.shape_grad(j, q) *
1201 * fe_face.shape_grad(i, q) *
dx;
1202 * ip_matrix_cc(i, j) += penalty_jump_val * mesh3_inv *
1203 * fe_face.shape_value(j, q) *
1204 * fe_face.shape_value(i, q) *
dx;
1212 * neighbor_cell = cell->neighbor(face_no);
1213 *
const unsigned int face_no_neighbor =
1214 * cell->neighbor_of_neighbor(face_no);
1218 * In the next step, we need to have a global way to compare the
1219 * cells in order to not calculate the same jump term twice:
1222 *
if (neighbor_cell->id() < cell->id())
1226 * fe_face_neighbor.reinit(neighbor_cell, face_no_neighbor);
1227 * neighbor_cell->get_dof_indices(local_dof_indices_neighbor);
1233 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1235 *
const double dx = fe_face.JxW(q);
1237 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1239 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1241 * ip_matrix_cc(i, j) +=
1242 * penalty_jump_grad * mesh_inv *
1243 * fe_face.shape_grad(j, q) *
1244 * fe_face.shape_grad(i, q) *
dx;
1245 * ip_matrix_cc(i, j) +=
1246 * penalty_jump_val * mesh3_inv *
1247 * fe_face.shape_value(j, q) *
1248 * fe_face.shape_value(i, q) *
dx;
1250 * ip_matrix_cn(i, j) -=
1251 * penalty_jump_grad * mesh_inv *
1252 * fe_face_neighbor.shape_grad(j, q) *
1253 * fe_face.shape_grad(i, q) *
dx;
1254 * ip_matrix_cn(i, j) -=
1255 * penalty_jump_val * mesh3_inv *
1256 * fe_face_neighbor.shape_value(j, q) *
1257 * fe_face.shape_value(i, q) *
dx;
1259 * ip_matrix_nc(i, j) -=
1260 * penalty_jump_grad * mesh_inv *
1261 * fe_face.shape_grad(j, q) *
1262 * fe_face_neighbor.shape_grad(i, q) *
dx;
1263 * ip_matrix_nc(i, j) -=
1264 * penalty_jump_val * mesh3_inv *
1265 * fe_face.shape_value(j, q) *
1266 * fe_face_neighbor.shape_value(i, q) *
dx;
1268 * ip_matrix_nn(i, j) +=
1269 * penalty_jump_grad * mesh_inv *
1270 * fe_face_neighbor.shape_grad(j, q) *
1271 * fe_face_neighbor.shape_grad(i, q) *
dx;
1272 * ip_matrix_nn(i, j) +=
1273 * penalty_jump_val * mesh3_inv *
1274 * fe_face_neighbor.shape_value(j, q) *
1275 * fe_face_neighbor.shape_value(i, q) *
dx;
1283 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1285 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1287 *
matrix(local_dof_indices[i], local_dof_indices[j]) +=
1288 * ip_matrix_cc(i, j);
1294 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1296 *
for (
unsigned int j = 0; j < n_dofs; ++j)
1298 *
matrix(local_dof_indices[i],
1299 * local_dof_indices_neighbor[j]) +=
1300 * ip_matrix_cn(i, j);
1301 *
matrix(local_dof_indices_neighbor[i],
1302 * local_dof_indices[j]) += ip_matrix_nc(i, j);
1303 *
matrix(local_dof_indices_neighbor[i],
1304 * local_dof_indices_neighbor[j]) +=
1305 * ip_matrix_nn(i, j);
1319 * <a name=
"step_82-BiLaplacianLDGLiftassemble_rhs"></a>
1320 * <h4>BiLaplacianLDGLift::assemble_rhs</h4>
1324 * This function
assemble the right-hand side of the system. Since we consider
1325 * homogeneous Dirichlet boundary conditions, imposed weakly in the bilinear
1326 * form
using the Nitsche approach, it only involves the contribution of the
1327 * forcing term @f$\int_{\Omega}fv_h@f$.
1330 *
template <
int dim>
1331 *
void BiLaplacianLDGLift<dim>::assemble_rhs()
1339 *
const unsigned int n_dofs = fe_values.dofs_per_cell;
1340 *
const unsigned int n_quad_pts = quad.size();
1342 *
const RightHandSide<dim> right_hand_side;
1345 * std::vector<types::global_dof_index> local_dof_indices(n_dofs);
1347 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1349 * fe_values.
reinit(cell);
1350 * cell->get_dof_indices(local_dof_indices);
1353 *
for (
unsigned int q = 0; q < n_quad_pts; ++q)
1355 *
const double dx = fe_values.JxW(q);
1357 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1360 * right_hand_side.value(fe_values.quadrature_point(q)) *
1361 * fe_values.shape_value(i, q) *
dx;
1365 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1366 * rhs(local_dof_indices[i]) += local_rhs(i);
1375 * <a name=
"step_82-BiLaplacianLDGLiftsolve"></a>
1376 * <h4>BiLaplacianLDGLift::solve</h4>
1380 * To solve the linear system @f$A\boldsymbol{U}=\boldsymbol{
F}@f$,
1381 * we proceed as in @ref step_74
"step-74" and use a direct method. We could
1382 * as well use an iterative method,
for instance the conjugate
1383 *
gradient method as in @ref step_3
"step-3".
1386 *
template <
int dim>
1387 *
void BiLaplacianLDGLift<dim>::solve()
1391 * A_direct.vmult(solution, rhs);
1399 * <a name=
"step_82-BiLaplacianLDGLiftcompute_errors"></a>
1400 * <h4>BiLaplacianLDGLift::compute_errors</h4>
1404 * This function computes the discrete @f$H^2@f$, @f$H^1@f$ and @f$L^2@f$ norms of
1405 * the error @f$u-u_h@f$, where @f$u@f$ is the exact solution and @f$u_h@f$ is
1406 * the
approximate solution. See the introduction
for the definition
1410 *
template <
int dim>
1411 *
void BiLaplacianLDGLift<dim>::compute_errors()
1413 *
double error_H2 = 0;
1414 *
double error_H1 = 0;
1415 *
double error_L2 = 0;
1418 *
const QGauss<dim - 1> quad_face(fe.degree + 1);
1434 *
const unsigned int n_q_points = quad.size();
1435 *
const unsigned int n_q_points_face = quad_face.size();
1439 * We introduce some variables
for the exact solution...
1442 *
const ExactSolution<dim> u_exact;
1449 * std::vector<double> solution_values_cell(n_q_points);
1450 * std::vector<Tensor<1, dim>> solution_gradients_cell(n_q_points);
1451 * std::vector<Tensor<2, dim>> solution_hessians_cell(n_q_points);
1453 * std::vector<double> solution_values(n_q_points_face);
1454 * std::vector<double> solution_values_neigh(n_q_points_face);
1455 * std::vector<Tensor<1, dim>> solution_gradients(n_q_points_face);
1456 * std::vector<Tensor<1, dim>> solution_gradients_neigh(n_q_points_face);
1458 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1460 * fe_values.
reinit(cell);
1462 * fe_values.get_function_values(solution, solution_values_cell);
1463 * fe_values.get_function_gradients(solution, solution_gradients_cell);
1464 * fe_values.get_function_hessians(solution, solution_hessians_cell);
1468 * We
first add the <i>bulk</i> terms.
1471 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1473 *
const double dx = fe_values.JxW(q);
1475 * error_H2 += (u_exact.hessian(fe_values.quadrature_point(q)) -
1476 * solution_hessians_cell[q])
1479 * error_H1 += (u_exact.gradient(fe_values.quadrature_point(q)) -
1480 * solution_gradients_cell[q])
1483 * error_L2 += Utilities::fixed_power<2>(
1484 * u_exact.value(fe_values.quadrature_point(q)) -
1485 * solution_values_cell[q]) *
1491 * We then add the face contributions.
1494 *
for (
unsigned int face_no = 0; face_no < cell->n_faces(); ++face_no)
1497 * cell->face(face_no);
1499 *
const double mesh_inv = 1.0 / face->diameter();
1500 *
const double mesh3_inv =
1501 * 1.0 / Utilities::fixed_power<3>(face->diameter());
1503 * fe_face.reinit(cell, face_no);
1505 * fe_face.get_function_values(solution, solution_values);
1506 * fe_face.get_function_gradients(solution, solution_gradients);
1508 *
const bool at_boundary = face->at_boundary();
1511 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1513 *
const double dx = fe_face.JxW(q);
1514 *
const double u_exact_q =
1515 * u_exact.value(fe_face.quadrature_point(q));
1517 * u_exact.gradient(fe_face.quadrature_point(q));
1521 * (u_exact_grad_q - solution_gradients[q]).
norm_square() *
1523 * error_H2 += mesh3_inv *
1524 * Utilities::fixed_power<2>(u_exact_q -
1525 * solution_values[q]) *
1527 * error_H1 += mesh_inv *
1528 * Utilities::fixed_power<2>(u_exact_q -
1529 * solution_values[q]) *
1537 * neighbor_cell = cell->neighbor(face_no);
1538 *
const unsigned int face_no_neighbor =
1539 * cell->neighbor_of_neighbor(face_no);
1543 * In the next step, we need to have a global way to compare the
1544 * cells in order to not calculate the same jump term twice:
1547 *
if (neighbor_cell->id() < cell->id())
1551 * fe_face_neighbor.reinit(neighbor_cell, face_no_neighbor);
1553 * fe_face.get_function_values(solution, solution_values);
1554 * fe_face_neighbor.get_function_values(solution,
1555 * solution_values_neigh);
1556 * fe_face.get_function_gradients(solution,
1557 * solution_gradients);
1558 * fe_face_neighbor.get_function_gradients(
1559 * solution, solution_gradients_neigh);
1561 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1563 *
const double dx = fe_face.JxW(q);
1567 * To compute the jump term, we use the fact that
1568 * @f$\jump{u}=0@f$ and
1569 * @f$\jump{\nabla u}=\mathbf{0}@f$ since @f$u\in
1575 * (solution_gradients_neigh[q] - solution_gradients[q])
1580 * Utilities::fixed_power<2>(solution_values_neigh[q] -
1581 * solution_values[q]) *
1585 * Utilities::fixed_power<2>(solution_values_neigh[q] -
1586 * solution_values[q]) *
1601 * std::cout <<
"DG H2 norm of the error: " << error_H2 << std::endl;
1602 * std::cout <<
"DG H1 norm of the error: " << error_H1 << std::endl;
1603 * std::cout <<
" L2 norm of the error: " << error_L2 << std::endl;
1611 * <a name=
"step_82-BiLaplacianLDGLiftoutput_results"></a>
1612 * <h4>BiLaplacianLDGLift::output_results</h4>
1616 * This function, which writes the solution to a
vtk file,
1617 * is copied from @ref step_3
"step-3".
1620 *
template <
int dim>
1621 *
void BiLaplacianLDGLift<dim>::output_results() const
1625 * data_out.add_data_vector(solution,
"solution");
1626 * data_out.build_patches();
1628 * std::ofstream output(
"solution.vtk");
1629 * data_out.write_vtk(output);
1637 * <a name=
"step_82-BiLaplacianLDGLiftassemble_local_matrix"></a>
1638 * <h4>BiLaplacianLDGLift::assemble_local_matrix</h4>
1642 * As already mentioned above,
this function is used to
assemble
1643 * the (local) mass matrices needed
for the computations of the
1644 * lifting terms. We reiterate that only the basis
functions with
1645 * support on the current cell are considered.
1648 *
template <
int dim>
1649 *
void BiLaplacianLDGLift<dim>::assemble_local_matrix(
1651 *
const unsigned int n_q_points,
1656 *
const unsigned int n_dofs = fe_values_lift.dofs_per_cell;
1659 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1661 *
const double dx = fe_values_lift.JxW(q);
1663 *
for (
unsigned int m = 0; m < n_dofs; ++m)
1664 *
for (
unsigned int n = 0; n < n_dofs; ++n)
1666 * local_matrix(m, n) +=
1667 * scalar_product(fe_values_lift[tau_ext].
value(n, q),
1668 * fe_values_lift[tau_ext].
value(m, q)) *
1679 * <a name=
"step_82-BiLaplacianLDGLiftcompute_discrete_hessians"></a>
1680 * <h4>BiLaplacianLDGLift::compute_discrete_hessians</h4>
1684 * This function is the main novelty of
this program. It computes
1685 * the discrete Hessian @f$H_h(\varphi)@f$
for all the basis
functions
1686 * @f$\varphi@f$ of @f$\mathbb{V}_h@f$ supported on the current cell and
1687 * those supported on a neighboring cell. The
first argument
1688 * indicates the current cell (referring to the global
DoFHandler
1689 *
object),
while the other two arguments are output variables that
1690 * are filled by
this function.
1694 * In the following, we need to evaluate finite element shape
1695 *
functions for the `fe_lift` finite element on the current
1696 * cell. Like
for example in @ref step_61
"step-61",
this "lift" space is defined
1697 * on every cell individually; as a consequence, there is no global
1698 *
DoFHandler associated with
this because we simply have no need
1699 *
for such a
DoFHandler. That leaves the question of what we should
1701 * them to evaluate shape
functions of `fe_lift` on a concrete
1702 * cell. If we simply provide the
first argument to
this function,
1704 * that the given `cell` belongs to a
DoFHandler that has a
1705 * different finite element associated with it than the `fe_lift`
1706 *
object we want to evaluate. Fortunately, there is a relatively
1708 * points into a
triangulation -- the same cell, but not associated
1709 * with a
DoFHandler, and consequently no finite element space. In
1710 * that case,
FEValues::reinit() will skip the check that would
1711 * otherwise lead to an error message. All we have to do is to convert
1713 * see the
first couple of lines of the function below to see how
1717 * template <
int dim>
1718 *
void BiLaplacianLDGLift<dim>::compute_discrete_hessians(
1719 * const typename
DoFHandler<dim>::active_cell_iterator &cell,
1720 *
std::vector<
std::vector<
Tensor<2, dim>>> &discrete_hessians,
1722 * &discrete_hessians_neigh)
1728 *
const QGauss<dim - 1> quad_face(
fe.degree + 1);
1730 *
const unsigned int n_q_points = quad.size();
1731 *
const unsigned int n_q_points_face = quad_face.size();
1735 * The information we need from the basis
functions of
1736 * @f$\mathbb{V}_h@f$: <code>fe_values</code> is needed to add
1737 * the broken Hessian part of the discrete Hessian,
while
1738 * <code>fe_face</code> and <code>fe_face_neighbor</code>
1739 * are used to compute the right-hand sides
for the local
1751 *
const unsigned int n_dofs = fe_values.dofs_per_cell;
1755 * The information needed from the basis
functions
1756 * of the finite element space
for the lifting terms:
1757 * <code>fe_values_lift</code> is used
for the (local)
1758 * mass
matrix (see @f$\boldsymbol{M}_c@f$ in the introduction),
1759 * while <code>fe_face_lift</code> is used to compute the
1760 * right-hand sides (see @f$\boldsymbol{G}_c@f$
for @f$b_e@f$).
1772 *
const unsigned int n_dofs_lift = fe_values_lift.dofs_per_cell;
1775 *
Vector<double> local_rhs_re(n_dofs_lift), local_rhs_be(n_dofs_lift),
1776 * coeffs_re(n_dofs_lift), coeffs_be(n_dofs_lift), coeffs_tmp(n_dofs_lift);
1781 *
double factor_avg;
1783 * fe_values.reinit(cell);
1784 * fe_values_lift.reinit(cell_lift);
1788 * We start by assembling the (local) mass
matrix used
for the computation
1789 * of the lifting terms @f$r_e@f$ and @f$b_e@f$.
1792 * assemble_local_matrix(fe_values_lift, n_q_points, local_matrix_lift);
1794 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1795 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1797 * discrete_hessians[i][q] = 0;
1799 *
for (
unsigned int face_no = 0;
1800 * face_no < discrete_hessians_neigh.size();
1803 * discrete_hessians_neigh[face_no][i][q] = 0;
1810 * @f$x_q@f$ of <code>cell</code>
for each basis function supported on
1811 * <code>cell</code>, namely we fill-in the variable
1812 * <code>discrete_hessians[i][q]</code>. For the lifting terms, we need to
1813 * add the contribution of all the faces of <code>cell</code>.
1816 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1821 *
for (
unsigned int face_no = 0; face_no < cell->n_faces(); ++face_no)
1824 * cell->face(face_no);
1826 *
const bool at_boundary = face->at_boundary();
1830 * Recall that by convention, the average of a function across a
1831 * boundary face @f$e@f$ reduces to the
trace of the function on the
1832 * only element adjacent to @f$e@f$, namely there is no factor
1833 * @f$\frac{1}{2}@f$. We distinguish between the two cases (the current
1834 * face lies in the interior or on the boundary of the domain)
using
1835 * the variable <code>factor_avg</code>.
1844 * fe_face.reinit(cell, face_no);
1845 * fe_face_lift.reinit(cell_lift, face_no);
1848 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1850 *
const double dx = fe_face_lift.JxW(q);
1854 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
1856 * local_rhs_re(m) +=
1858 * (fe_face_lift[tau_ext].
value(m, q) * normal) *
1859 * fe_face.shape_grad(i, q) *
dx;
1865 * Here, <code>local_rhs_be(m)</code> corresponds to @f$G_m@f$
1866 * introduced in the comments about the implementation of the
1867 * lifting @f$b_e@f$ in the
case
1868 * @f$\varphi=\varphi^c@f$.
1872 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1874 *
const double dx = fe_face_lift.JxW(q);
1878 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
1880 * local_rhs_be(m) += factor_avg *
1881 * fe_face_lift[tau_ext].divergence(m, q) *
1882 * normal * fe_face.shape_value(i, q) *
dx;
1887 * solver.solve(local_matrix_lift,
1891 * coeffs_re += coeffs_tmp;
1894 * solver.solve(local_matrix_lift,
1898 * coeffs_be += coeffs_tmp;
1902 *
for (
unsigned int q = 0; q < n_q_points; ++q)
1904 * discrete_hessians[i][q] += fe_values.shape_hessian(i, q);
1906 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
1908 * discrete_hessians[i][q] -=
1909 * coeffs_re[m] * fe_values_lift[tau_ext].value(m, q);
1912 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
1914 * discrete_hessians[i][q] +=
1915 * coeffs_be[m] * fe_values_lift[tau_ext].value(m, q);
1925 * @f$x_q@f$ of <code>cell</code>
for each basis function supported on a
1926 * neighboring <code>neighbor_cell</code> of <code>cell</code>, namely we
1927 * fill-in the variable <code>discrete_hessians_neigh[face_no][i][q]</code>.
1928 * For the lifting terms, we only need to add the contribution of the
1929 * face adjacent to <code>cell</code> and <code>neighbor_cell</code>.
1932 *
for (
unsigned int face_no = 0; face_no < cell->n_faces(); ++face_no)
1935 * cell->face(face_no);
1937 *
const bool at_boundary = face->at_boundary();
1943 * For non-homogeneous Dirichlet BCs, we would need to
1944 * compute the lifting of the prescribed BC (see the
1945 *
"Possible Extensions" section
for more details).
1952 * neighbor_cell = cell->neighbor(face_no);
1953 *
const unsigned int face_no_neighbor =
1954 * cell->neighbor_of_neighbor(face_no);
1955 * fe_face_neighbor.reinit(neighbor_cell, face_no_neighbor);
1957 *
for (
unsigned int i = 0; i < n_dofs; ++i)
1962 * fe_face_lift.reinit(cell_lift, face_no);
1965 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1967 *
const double dx = fe_face_lift.JxW(q);
1969 * fe_face_neighbor.normal_vector(q);
1971 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
1973 * local_rhs_re(m) +=
1974 * 0.5 * (fe_face_lift[tau_ext].value(m, q) * normal) *
1975 * fe_face_neighbor.shape_grad(i, q) *
dx;
1981 * Here, <code>local_rhs_be(m)</code> corresponds to @f$G_m@f$
1982 * introduced in the comments about the implementation of the
1983 * lifting @f$b_e@f$ in the
case
1984 * @f$\varphi=\varphi^n@f$.
1988 *
for (
unsigned int q = 0; q < n_q_points_face; ++q)
1990 *
const double dx = fe_face_lift.JxW(q);
1992 * fe_face_neighbor.normal_vector(q);
1994 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
1996 * local_rhs_be(m) +=
1997 * 0.5 * fe_face_lift[tau_ext].divergence(m, q) *
1998 * normal * fe_face_neighbor.shape_value(i, q) *
dx;
2002 * solver.solve(local_matrix_lift,
2006 * solver.solve(local_matrix_lift,
2011 *
for (
unsigned int q = 0; q < n_q_points; ++q)
2013 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
2015 * discrete_hessians_neigh[face_no][i][q] -=
2016 * coeffs_re[m] * fe_values_lift[tau_ext].value(m, q);
2019 *
for (
unsigned int m = 0; m < n_dofs_lift; ++m)
2021 * discrete_hessians_neigh[face_no][i][q] +=
2022 * coeffs_be[m] * fe_values_lift[tau_ext].value(m, q);
2036 * <a name=
"step_82-BiLaplacianLDGLiftrun"></a>
2037 * <h4>BiLaplacianLDGLift::run</h4>
2040 *
template <
int dim>
2041 *
void BiLaplacianLDGLift<dim>::run()
2046 * assemble_system();
2061 * <a name=
"step_82-Thecodemaincodefunction"></a>
2062 * <h3>The <code>main</code> function</h3>
2066 * This is the <code>main</code> function. We define here the number of mesh
2067 * refinements, the polynomial degree
for the two finite element spaces
2068 * (
for the solution and the two liftings) and the two penalty coefficients.
2069 * We can also change the dimension to run the code in 3
d.
2076 *
const unsigned int n_ref = 3;
2078 *
const unsigned int degree =
2081 *
const double penalty_grad =
2083 *
const double penalty_val =
2086 * Step82::BiLaplacianLDGLift<2> problem(n_ref,
2093 *
catch (std::exception &exc)
2095 * std::cerr << std::endl
2097 * <<
"----------------------------------------------------"
2099 * std::cerr <<
"Exception on processing: " << std::endl
2100 * << exc.what() << std::endl
2101 * <<
"Aborting!" << std::endl
2102 * <<
"----------------------------------------------------"
2108 * std::cerr << std::endl
2110 * <<
"----------------------------------------------------"
2112 * std::cerr <<
"Unknown exception!" << std::endl
2113 * <<
"Aborting!" << std::endl
2114 * <<
"----------------------------------------------------"
2122<a name=
"step_82-Results"></a><h1>Results</h1>
2126When running the program, the sparsity pattern is written to an
svg file, the solution is written to a
vtk file, and some results are printed to the console. With the current setup, the output should read
2130Number of active cells: 64
2131Number of degrees of freedom: 576
2132Assembling the system.............
2134DG H2
norm of the error: 0.0151063
2135DG H1
norm of the error: 0.000399747
2136 L2 norm of the error: 5.33856e-05
2140This corresponds to the bi-Laplacian problem with the manufactured solution mentioned above
for @f$d=2@f$, 3 refinements of the mesh, degree @f$k=2@f$, and @f$\gamma_0=\gamma_1=1@f$
for the penalty coefficients. By changing the number of refinements, we get the following results:
2142<table align=
"center" class=
"doxtable">
2155 <td align=
"center">1</td>
2156 <td align=
"right">4</td>
2157 <td align=
"right">36</td>
2158 <td align=
"center">5.651e-02</td>
2159 <td align=
"center">--</td>
2160 <td align=
"center">3.366e-03</td>
2161 <td align=
"center">--</td>
2162 <td align=
"center">3.473e-04</td>
2163 <td align=
"center">--</td>
2166 <td align=
"center">2</td>
2167 <td align=
"right">16</td>
2168 <td align=
"right">144</td>
2169 <td align=
"center">3.095e-02</td>
2170 <td align=
"center">0.87</td>
2171 <td align=
"center">1.284e-03</td>
2172 <td align=
"center">1.39</td>
2173 <td align=
"center">1.369e-04</td>
2174 <td align=
"center">1.34</td>
2177 <td align=
"center">3</td>
2178 <td align=
"right">64</td>
2179 <td align=
"right">576</td>
2180 <td align=
"center">1.511e-02</td>
2181 <td align=
"center">1.03</td>
2182 <td align=
"center">3.997e-04</td>
2183 <td align=
"center">1.68</td>
2184 <td align=
"center">5.339e-05</td>
2185 <td align=
"center">1.36</td>
2188 <td align=
"center">4</td>
2189 <td align=
"right">256</td>
2190 <td align=
"right">2304</td>
2191 <td align=
"center">7.353e-03</td>
2192 <td align=
"center">1.04</td>
2193 <td align=
"center">1.129e-04</td>
2194 <td align=
"center">1.82</td>
2195 <td align=
"center">1.691e-05</td>
2196 <td align=
"center">1.66</td>
2199 <td align=
"center">5</td>
2200 <td align=
"right">1024</td>
2201 <td align=
"right">9216</td>
2202 <td align=
"center">3.609e-03</td>
2203 <td align=
"center">1.03</td>
2204 <td align=
"center">3.024e-05</td>
2205 <td align=
"center">1.90</td>
2206 <td align=
"center">4.789e-06</td>
2207 <td align=
"center">1.82</td>
2210 <td align=
"center">6</td>
2211 <td align=
"right">4096</td>
2212 <td align=
"right">36864</td>
2213 <td align=
"center">1.785e-03</td>
2214 <td align=
"center">1.02</td>
2215 <td align=
"center">7.850e-06</td>
2216 <td align=
"center">1.95</td>
2217 <td align=
"center">1.277e-06</td>
2218 <td align=
"center">1.91</td>
2222This matches the expected optimal convergence rates
for the @f$H^2@f$ and
2223@f$H^1@f$ norms, but is sub-optimal
for the @f$L_2@f$
norm. Incidentally,
this
2224also matches the results seen in @ref step_47
"step-47" when
using polynomial degree
2227Indeed, just like in @ref step_47
"step-47", we can regain the optimal convergence
2228order
if we set the polynomial degree of the finite elements to @f$k=3@f$
2229or higher. Here are the
numbers for @f$k=3@f$:
2231<table align=
"center" class=
"doxtable">
2232 <tr> <th> n_ref </th> <th>
n_cells </th> <th> n_dofs </th> <th> error H2 </th> <th> rate </th> <th> error H1 </th> <th> rate </th> <th> error
L2 </th> <th> rate</th> </tr>
2233 <tr> <td> 1 </td> <td> 4 </td> <td> 36 </td> <td> 1.451e-02 </td> <td> -- </td> <td> 5.494e-04 </td> <td> -- </td> <td> 3.035e-05 </td> <td> --</td> </tr>
2234 <tr> <td> 2 </td> <td> 16 </td> <td> 144 </td> <td> 3.565e-03 </td> <td> 2.02 </td> <td> 6.870e-05 </td> <td> 3.00 </td> <td> 2.091e-06 </td> <td> 3.86</td> </tr>
2235 <tr> <td> 3 </td> <td> 64 </td> <td> 576 </td> <td> 8.891e-04 </td> <td> 2.00 </td> <td> 8.584e-06 </td> <td> 3.00 </td> <td> 1.352e-07 </td> <td> 3.95</td> </tr>
2236 <tr> <td> 4 </td> <td> 256 </td> <td> 2304 </td> <td> 2.223e-04 </td> <td> 2.00 </td> <td> 1.073e-06 </td> <td> 3.00 </td> <td> 8.594e-09 </td> <td> 3.98</td> </tr>
2237 <tr> <td> 5 </td> <td> 1024 </td> <td> 9216 </td> <td> 5.560e-05 </td> <td> 2.00 </td> <td> 1.341e-07 </td> <td> 3.00 </td> <td> 5.418e-10 </td> <td> 3.99</td> </tr>
2238 <tr> <td> 6 </td> <td> 4096 </td> <td> 36864 </td> <td> 1.390e-05 </td> <td> 2.00 </td> <td> 1.676e-08 </td> <td> 3.00 </td> <td> 3.245e-11 </td> <td> 4.06</td> </tr>
2242<a name=
"step_82-Possibleextensions"></a><h3>Possible extensions</h3>
2245The code can be easily adapted to deal with the following cases:
2248 <li>Non-homogeneous Dirichlet boundary conditions on (part of) the boundary @f$\partial \Omega@f$ of @f$\Omega@f$.</li>
2249 <li>Hanging-nodes (proceed as in @ref step_14
"step-14" to not visit a sub-face twice when computing the lifting terms in <code>compute_discrete_hessians</code> and the penalty terms in <code>assemble_matrix</code>).</li>
2250 <li>LDG method
for the Poisson problem (use the discrete gradient consisting of the broken gradient and the lifting of the jump of @f$u_h@f$).</li>
2253We give below additional details
for the
first of these points.
2256<a name=
"step_82-NonhomogeneousDirichletboundaryconditions"></a><h4>Non-homogeneous Dirichlet boundary conditions</h4>
2258If we prescribe non-homogeneous Dirichlet conditions, say
2260\nabla u=\mathbf{g} \quad \mbox{and} \quad u=g \qquad \mbox{on } \partial \Omega,
2262then the right-hand side @f$\boldsymbol{
F}@f$ of the linear system needs to be modified as follows
2264F_i:=\int_{\Omega}f\varphi_i-\sum_{e\in\mathcal{
E}_h^
b}\int_{\Omega}r_e(\mathbf{g}):H_h(\varphi_i)+\sum_{
e\in\mathcal{
E}_h^
b}\int_{\Omega}b_e(g):H_h(\varphi_i)+\gamma_1\sum_{
e\in\mathcal{
E}_h^
b}h_e^{-1}\int_e\mathbf{g}\cdot\nabla\varphi_i+\gamma_0\sum_{e\in\mathcal{
E}_h^
b}h_e^{-3}\int_e g\varphi_i, \qquad 1\leq i \leq N_h.
2266Note that
for any given
index @f$i@f$, many of the terms are zero. Indeed,
for @f$e\in \mathcal{
E}_h^
b@f$ we have @f${\rm supp}\,(r_e(\mathbf{g}))={\rm supp}\,(b_e(g))=K@f$, where @f$K@f$ is the element
for which @f$e\subset\partial K@f$. Therefore, the liftings @f$r_e(\mathbf{g})@f$ and @f$b_e(g)@f$ contribute to @f$F_i@f$ only
if @f$\varphi_i@f$ has support on @f$K@f$ or a neighbor of @f$K@f$. In other words, when integrating on a cell @f$K@f$, we need to add
2268\int_{
K}f\varphi_i+\sum_{e\in\mathcal{
E}_h^
b, e\subset\partial
K}\left[-\int_{
K}r_e(\mathbf{g}):H_h(\varphi_i)+\int_{
K}b_e(g):H_h(\varphi_i)+\gamma_1h_e^{-1}\int_e\mathbf{g}\cdot\nabla\varphi_i+\gamma_0h_e^{-3}\int_e g\varphi_i\right]
2270to @f$F_i@f$
for the indices @f$i@f$ such that @f$\varphi_i@f$ has support on @f$K@f$ and
2272\sum_{e\in\mathcal{
E}_h^
b, e\subset\partial
K}\left[-\int_{
K}r_e(\mathbf{g}):H_h(\varphi_i)+\int_{
K}b_e(g):H_h(\varphi_i)\right]
2274to @f$F_i@f$ for the indices @f$i@f$ such that @f$\varphi_i@f$ has support on a neighbor of @f$K@f$.
2277Note that we can easily consider the case where Dirichlet boundary conditions are imposed only on a subset @f$\emptyset\neq\Gamma_D\subset\partial \Omega@f$. In this case, we simply need to replace @f$\mathcal{
E}_h^
b@f$ by @f$\mathcal{
E}_h^D\subset\mathcal{
E}_h^
b@f$ consisting of the faces belonging to @f$\Gamma_D@f$. This also affects the
matrix @f$A@f$ (simply replace @f$\mathcal{
E}_h=\mathcal{
E}_h^0\cup\mathcal{
E}_h^
b@f$ by @f$\mathcal{
E}_h=\mathcal{
E}_h^0\cup\mathcal{
E}_h^D@f$).
2280<a name=
"step_82-PlainProg"></a>
2281<h1> The plain program</h1>
2282@include
"step-82.cc"
void attach_dof_handler(const DoFHandler< dim, spacedim > &)
const ObserverPointer< const FiniteElement< dim, spacedim >, FEValuesBase< dim, spacedim > > fe
const Quadrature< dim > quadrature
void reinit(const TriaIterator< DoFCellAccessor< dim, spacedim, level_dof_access > > &cell)
virtual SymmetricTensor< 2, dim, RangeNumberType > hessian(const Point< dim > &p, const unsigned int component=0) const
virtual Tensor< 1, dim, RangeNumberType > gradient(const Point< dim > &p, const unsigned int component=0) const
virtual RangeNumberType value(const Point< dim > &p, const unsigned int component=0) const
void initialize(const SparsityPattern &sparsity_pattern)
constexpr numbers::NumberTraits< Number >::real_type norm_square() const
typename ActiveSelector::face_iterator face_iterator
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())
void make_flux_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern)
@ update_hessians
Second derivatives of shape functions.
@ 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.
#define DEAL_II_NOT_IMPLEMENTED()
CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt K
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 hyper_cube(Triangulation< dim, spacedim > &tria, const double left=0., const double right=1., const bool colorize=false)
@ matrix
Contents is actually a matrix.
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim > > > &Du)
void L2(Vector< number > &result, const FEValuesBase< dim > &fe, const std::vector< double > &input, const double factor=1.)
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 > e(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > b(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
Tensor< 2, dim, Number > F(const Tensor< 2, dim, Number > &Grad_u)
VectorType::value_type * end(VectorType &V)
unsigned int n_cells(const internal::TriangulationImplementation::NumberCache< 1 > &c)
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 > sqrt(const ::VectorizedArray< Number, width > &)
unsigned int global_dof_index
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
DEAL_II_HOST constexpr Number trace(const SymmetricTensor< 2, dim2, Number > &)