492 *
return (-8. * p[0] * p[1]);
497 *
double RightHandSide<3>::value(
const Point<3> &p,
498 *
const unsigned int )
const
533 * normal /= p.
norm();
535 *
return (-
trace(hessian) + 2 * (gradient * normal) +
536 * (hessian * normal) * normal);
543 * <a name=
"step_38-ImplementationofthecodeLaplaceBeltramiProblemcodeclass"></a>
544 * <h3>Implementation of the <code>LaplaceBeltramiProblem</code>
class</h3>
548 * The rest of the program is actually quite unspectacular
if you know
549 * @ref step_4
"step-4". Our first step is to define the constructor, setting the
550 * polynomial degree of the finite element and
mapping, and associating the
551 * DoF handler to the triangulation:
554 *
template <
int spacedim>
555 * LaplaceBeltramiProblem<spacedim>::LaplaceBeltramiProblem(
556 *
const unsigned degree)
558 * , dof_handler(triangulation)
566 * <a name=
"step_38-LaplaceBeltramiProblemmake_grid_and_dofs"></a>
567 * <h4>LaplaceBeltramiProblem::make_grid_and_dofs</h4>
571 * The next step is to create the mesh, distribute degrees of freedom, and
572 * set up the various variables that describe the linear system. All of
573 * these steps are standard with the exception of how to create a mesh that
574 * describes a surface. We could generate a mesh
for the domain we are
575 * interested in, generate a triangulation
using a mesh generator, and read
576 * it in
using the
GridIn class. Or, as we
do here, we generate the mesh
581 * In particular, what we
're going to do is this (enclosed between the set
582 * of braces below): we generate a <code>spacedim</code> dimensional mesh
583 * for the half disk (in 2d) or half ball (in 3d), using the
584 * GridGenerator::half_hyper_ball function. This function sets the boundary
585 * indicators of all faces on the outside of the boundary to zero for the
586 * ones located on the perimeter of the disk/ball, and one on the straight
587 * part that splits the full disk/ball into two halves. The next step is the
588 * main point: The GridGenerator::extract_boundary_mesh function creates a
589 * mesh that consists of those cells that are the faces of the previous mesh,
590 * i.e. it describes the <i>surface</i> cells of the original (volume)
591 * mesh. However, we do not want all faces: only those on the perimeter of
592 * the disk or ball which carry boundary indicator zero; we can select these
593 * cells using a set of boundary indicators that we pass to
594 * GridGenerator::extract_boundary_mesh.
598 * There is one point that needs to be mentioned. In order to refine a
599 * surface mesh appropriately if the manifold is curved (similarly to
600 * refining the faces of cells that are adjacent to a curved boundary), the
601 * triangulation has to have an object attached to it that describes where
602 * new vertices should be located. If you don't attach such a boundary
603 * object, they will be located halfway between existing vertices;
this is
604 * appropriate
if you have a domain with straight boundaries (
e.g. a
605 * polygon) but not when, as here, the manifold has curvature. So
for things
606 * to work properly, we need to attach a manifold
object to our (surface)
607 * triangulation, in much the same way as we
've already done in 1d for the
608 * boundary. We create such an object and attach it to the triangulation.
612 * The final step in creating the mesh is to refine it a number of
613 * times. The rest of the function is the same as in previous tutorial
617 * template <int spacedim>
618 * void LaplaceBeltramiProblem<spacedim>::make_grid_and_dofs()
621 * Triangulation<spacedim> volume_mesh;
622 * GridGenerator::half_hyper_ball(volume_mesh);
624 * const std::set<types::boundary_id> boundary_ids = {0};
626 * GridGenerator::extract_boundary_mesh(volume_mesh,
630 * triangulation.set_all_manifold_ids(0);
631 * triangulation.set_manifold(0, SphericalManifold<dim, spacedim>());
633 * triangulation.refine_global(4);
635 * std::cout << "Surface mesh has " << triangulation.n_active_cells()
636 * << " cells." << std::endl;
638 * dof_handler.distribute_dofs(fe);
640 * std::cout << "Surface mesh has " << dof_handler.n_dofs()
641 * << " degrees of freedom." << std::endl;
643 * DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
644 * DoFTools::make_sparsity_pattern(dof_handler, dsp);
645 * sparsity_pattern.copy_from(dsp);
647 * system_matrix.reinit(sparsity_pattern);
649 * solution.reinit(dof_handler.n_dofs());
650 * system_rhs.reinit(dof_handler.n_dofs());
657 * <a name="step_38-LaplaceBeltramiProblemassemble_system"></a>
658 * <h4>LaplaceBeltramiProblem::assemble_system</h4>
662 * The following is the central function of this program, assembling the
663 * matrix that corresponds to the surface Laplacian (Laplace-Beltrami
664 * operator). Maybe surprisingly, it actually looks exactly the same as for
665 * the regular Laplace operator discussed in, for example, @ref step_4 "step-4". The key
666 * is that the FEValues::shape_grad() function does the magic: It returns
667 * the surface gradient @f$\nabla_K \phi_i(x_q)@f$ of the @f$i@f$th shape function
668 * at the @f$q@f$th quadrature point. The rest then does not need any changes
672 * template <int spacedim>
673 * void LaplaceBeltramiProblem<spacedim>::assemble_system()
678 * const QGauss<dim> quadrature_formula(2 * fe.degree);
679 * FEValues<dim, spacedim> fe_values(mapping,
681 * quadrature_formula,
682 * update_values | update_gradients |
683 * update_quadrature_points |
684 * update_JxW_values);
686 * const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
687 * const unsigned int n_q_points = quadrature_formula.size();
689 * FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
690 * Vector<double> cell_rhs(dofs_per_cell);
692 * std::vector<double> rhs_values(n_q_points);
693 * std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
695 * RightHandSide<spacedim> rhs;
697 * for (const auto &cell : dof_handler.active_cell_iterators())
702 * fe_values.reinit(cell);
704 * rhs.value_list(fe_values.get_quadrature_points(), rhs_values);
706 * for (unsigned int i = 0; i < dofs_per_cell; ++i)
707 * for (unsigned int j = 0; j < dofs_per_cell; ++j)
708 * for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
709 * cell_matrix(i, j) += fe_values.shape_grad(i, q_point) *
710 * fe_values.shape_grad(j, q_point) *
711 * fe_values.JxW(q_point);
713 * for (unsigned int i = 0; i < dofs_per_cell; ++i)
714 * for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
715 * cell_rhs(i) += fe_values.shape_value(i, q_point) *
716 * rhs_values[q_point] * fe_values.JxW(q_point);
718 * cell->get_dof_indices(local_dof_indices);
719 * for (unsigned int i = 0; i < dofs_per_cell; ++i)
721 * for (unsigned int j = 0; j < dofs_per_cell; ++j)
722 * system_matrix.add(local_dof_indices[i],
723 * local_dof_indices[j],
724 * cell_matrix(i, j));
726 * system_rhs(local_dof_indices[i]) += cell_rhs(i);
730 * std::map<types::global_dof_index, double> boundary_values;
731 * VectorTools::interpolate_boundary_values(
732 * mapping, dof_handler, 0, Solution<spacedim>(), boundary_values);
734 * MatrixTools::apply_boundary_values(
735 * boundary_values, system_matrix, solution, system_rhs, false);
743 * <a name="step_38-LaplaceBeltramiProblemsolve"></a>
744 * <h4>LaplaceBeltramiProblem::solve</h4>
748 * The next function is the one that solves the linear system. Here, too, no
749 * changes are necessary:
752 * template <int spacedim>
753 * void LaplaceBeltramiProblem<spacedim>::solve()
755 * SolverControl solver_control(solution.size(), 1e-7 * system_rhs.l2_norm());
756 * SolverCG<Vector<double>> cg(solver_control);
758 * PreconditionSSOR<SparseMatrix<double>> preconditioner;
759 * preconditioner.initialize(system_matrix, 1.2);
761 * cg.solve(system_matrix, solution, system_rhs, preconditioner);
769 * <a name="step_38-LaplaceBeltramiProblemoutput_result"></a>
770 * <h4>LaplaceBeltramiProblem::output_result</h4>
774 * This is the function that generates graphical output from the
775 * solution. Most of it is boilerplate code, but there are two points worth
780 * - The DataOut::add_data_vector() function can take two kinds of vectors:
781 * Either vectors that have one value per degree of freedom defined by the
782 * DoFHandler object previously attached via DataOut::attach_dof_handler();
783 * and vectors that have one value for each cell of the triangulation, for
784 * example to output estimated errors for each cell. Typically, the
785 * DataOut class knows to tell these two kinds of vectors apart: there are
786 * almost always more degrees of freedom than cells, so we can
787 * differentiate by the two kinds looking at the length of a vector. We
788 * could do the same here, but only because we got lucky: we use a half
789 * sphere. If we had used the whole sphere as domain and @f$Q_1@f$ elements,
790 * we would have the same number of cells as vertices and consequently the
791 * two kinds of vectors would have the same number of elements. To avoid
792 * the resulting confusion, we have to tell the DataOut::add_data_vector()
793 * function which kind of vector we have: DoF data. This is what the third
794 * argument to the function does.
795 * - The DataOut::build_patches() function can generate output that subdivides
796 * each cell so that visualization programs can resolve curved manifolds
797 * or higher polynomial degree shape functions better. We here subdivide
798 * each element in each coordinate direction as many times as the
799 * polynomial degree of the finite element in use.
802 * template <int spacedim>
803 * void LaplaceBeltramiProblem<spacedim>::output_results() const
805 * DataOut<dim, spacedim> data_out;
806 * data_out.attach_dof_handler(dof_handler);
807 * data_out.add_data_vector(solution,
809 * DataOut<dim, spacedim>::type_dof_data);
810 * data_out.build_patches(mapping, mapping.get_degree());
812 * const std::string filename =
813 * "solution-" + std::to_string(spacedim) + "d.vtk";
814 * std::ofstream output(filename);
815 * data_out.write_vtk(output);
823 * <a name="step_38-LaplaceBeltramiProblemcompute_error"></a>
824 * <h4>LaplaceBeltramiProblem::compute_error</h4>
828 * This is the last piece of functionality: we want to compute the error in
829 * the numerical solution. It is a verbatim copy of the code previously
830 * shown and discussed in @ref step_7 "step-7". As mentioned in the introduction, the
831 * <code>Solution</code> class provides the (tangential) gradient of the
832 * solution. To avoid evaluating the error only a superconvergence points,
833 * we choose a quadrature rule of sufficiently high order.
836 * template <int spacedim>
837 * void LaplaceBeltramiProblem<spacedim>::compute_error() const
839 * Vector<float> difference_per_cell(triangulation.n_active_cells());
840 * VectorTools::integrate_difference(mapping,
843 * Solution<spacedim>(),
844 * difference_per_cell,
845 * QGauss<dim>(2 * fe.degree + 1),
846 * VectorTools::H1_norm);
848 * double h1_error = VectorTools::compute_global_error(triangulation,
849 * difference_per_cell,
850 * VectorTools::H1_norm);
851 * std::cout << "H1 error = " << h1_error << std::endl;
859 * <a name="step_38-LaplaceBeltramiProblemrun"></a>
860 * <h4>LaplaceBeltramiProblem::run</h4>
864 * The last function provides the top-level logic. Its contents are
868 * template <int spacedim>
869 * void LaplaceBeltramiProblem<spacedim>::run()
871 * make_grid_and_dofs();
877 * } // namespace Step38
883 * <a name="step_38-Themainfunction"></a>
884 * <h3>The main() function</h3>
888 * The remainder of the program is taken up by the <code>main()</code>
889 * function. It follows exactly the general layout first introduced in @ref step_6 "step-6"
890 * and used in all following tutorial programs:
897 * using namespace Step38;
899 * LaplaceBeltramiProblem<3> laplace_beltrami;
900 * laplace_beltrami.run();
902 * catch (std::exception &exc)
904 * std::cerr << std::endl
906 * << "----------------------------------------------------"
908 * std::cerr << "Exception on processing: " << std::endl
909 * << exc.what() << std::endl
910 * << "Aborting!" << std::endl
911 * << "----------------------------------------------------"
917 * std::cerr << std::endl
919 * << "----------------------------------------------------"
921 * std::cerr << "Unknown exception!" << std::endl
922 * << "Aborting!" << std::endl
923 * << "----------------------------------------------------"
931<a name="step_38-Results"></a><h1>Results</h1>
934When you run the program, the following output should be printed on screen:
937Surface mesh has 1280 cells.
938Surface mesh has 5185 degrees of freedom.
943By playing around with the number of global refinements in the
944<code>LaplaceBeltrami::make_grid_and_dofs</code> function you increase or decrease mesh
945refinement. For example, doing one more refinement and only running the 3d surface
946problem yields the following
950Surface mesh has 5120 cells.
951Surface mesh has 20609 degrees of freedom.
955This is what we expect: make the mesh size smaller by a factor of two and the
956error goes down by a factor of four (remember that we use bi-quadratic
957elements). The full sequence of errors from one to five refinements looks like
958this, neatly following the theoretically predicted pattern:
967Finally, the program produces graphical output that we can visualize. Here is
968a plot of the results:
970<img src="https://www.dealii.org/images/steps/developer/step-38.solution-3d.png" alt="">
972The program also works for 1d curves in 2d, not just 2d surfaces in 3d. You
973can test this by changing the template argument in <code>main()</code> like
976 LaplaceBeltramiProblem<2> laplace_beltrami;
978The domain is a curve in 2d, and we can visualize the solution by using the
979third dimension (and color) to denote the value of the function @f$u(x)@f$. This
980then looks like so (the white curve is the domain, the colored curve is the
981solution extruded into the third dimension, clearly showing the change in sign
982as the curve moves from one quadrant of the domain into the adjacent one):
984<img src="https://www.dealii.org/images/steps/developer/step-38.solution-2d.png" alt="">
987<a name="step-38-extensions"></a>
988<a name="step_38-Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
991Computing on surfaces only becomes interesting if the surface is more
992interesting than just a half sphere. To achieve this, deal.II can read
993meshes that describe surfaces through the usual GridIn class. Or, in case you
994have an analytic description, a simple mesh can sometimes be stretched and
995bent into a shape we are interested in.
997Let us consider a relatively simple example: we take the half sphere we used
998before, we stretch it by a factor of 10 in the z-direction, and then we jumble
999the x- and y-coordinates a bit. Let's show the computational domain and the
1000solution first before we go into details of the implementation below:
1002<img src=
"https://www.dealii.org/images/steps/developer/step-38.warp-1.png" alt=
"">
1004<img src=
"https://www.dealii.org/images/steps/developer/step-38.warp-2.png" alt=
"">
1007function. It needs a way to transform each individual mesh point to a
1008different position. Let us here use the following, rather simple function
1009(remember: stretch in one direction, jumble in the other two):
1012template <
int spacedim>
1013Point<spacedim> warp(const
Point<spacedim> &p)
1016 q[spacedim-1] *= 10;
1027If we followed the <code>LaplaceBeltrami::make_grid_and_dofs</code> function, we would
1028extract the half spherical surface mesh as before, warp it into the shape we
1029want, and
refine as often as necessary. This is not quite as simple as we
'd
1030like here, though: refining requires that we have an appropriate manifold
1031object attached to the triangulation that describes where new vertices of the
1032mesh should be located upon refinement. I'm sure it
's possible to describe
1033this manifold in a not-too-complicated way by simply undoing the
1034transformation above (yielding the spherical surface again), finding the
1035location of a new point on the sphere, and then re-warping the result. But I'm
1036a lazy person, and since doing
this is not really the
point here, let
's just
1037make our lives a bit easier: we'll
extract the half sphere,
refine it as
1038often as necessary, get rid of the
object that describes the manifold since we
1039now no longer need it, and then
finally warp the mesh. With the function
1040above,
this would look as follows:
1043template <
int spacedim>
1044void LaplaceBeltrami<spacedim>::make_grid_and_dofs()
1052 const std::set<types::boundary_id> boundary_ids = {0};
1057 std::ofstream x(
"x"), y(
"y");
1062 std::cout <<
"Surface mesh has " << triangulation.n_active_cells()
1069Note that the only essential addition is the line marked with
1070asterisks. It is worth pointing out
one other thing here, though: because we
1071detach the manifold description from the surface mesh, whenever we use a
1072mapping object in the rest of the program, it has no curved boundary
1073description to go on any more. Rather, it will have to use the implicit,
1074FlatManifold class that is used on all parts of the domain not
1075explicitly assigned a different manifold object. Consequently, whether we use
1077using a bilinear approximation. (That is of course undesirable, and can be
1078fixed by creating a manifold description of the domain you have, and attaching
1079this manifold to the triangulation.)
1081All these drawbacks aside, the resulting pictures are still pretty. The only
1082other differences to what's in @ref step_38
"step-38" is that we changed the right hand side
1083to @f$f(\mathbf x)=\
sin x_3@f$ and the boundary values (through the
1084<code>Solution</code>
class) to @f$u(\mathbf x)|_{\partial\Omega}=\
cos x_3@f$. Of
1085course, we now no longer know the exact solution, so the computation of the
1086error at the
end of <code>LaplaceBeltrami::run</code> will yield a meaningless
1090<a name=
"step_38-PlainProg"></a>
1091<h1> The plain program</h1>
1092@include
"step-38.cc"
void write_gnuplot(const Triangulation< dim, spacedim > &tria, std::ostream &out, const Mapping< dim, spacedim > *mapping=nullptr) const
numbers::NumberTraits< Number >::real_type norm() const
void refine_global(const unsigned int times=1)
MappingQ< dim, spacedim > StaticMappingQ1< dim, spacedim >::mapping
return_type extract_boundary_mesh(const MeshType< dim, spacedim > &volume_mesh, MeshType< dim - 1, spacedim > &surface_mesh, const std::set< types::boundary_id > &boundary_ids=std::set< types::boundary_id >())
void half_hyper_ball(Triangulation< dim > &tria, const Point< dim > ¢er=Point< dim >(), const double radius=1.)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
constexpr types::blas_int one
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
constexpr ReturnType< rank, T >::value_type & extract(T &t, const ArrayType &indices)
VectorType::value_type * end(VectorType &V)
::VectorizedArray< Number, width > exp(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
DEAL_II_HOST constexpr Number trace(const SymmetricTensor< 2, dim2, Number > &)