Reference documentation for deal.II version 9.2.0
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
The step-16b tutorial program

This tutorial depends on step-16.

Table of contents
  1. Introduction
  2. The commented program
  1. Results
  2. The plain program

Introduction

This is a variant of step-16 with the only change that we are using the MeshWorker framework with the pre-made LocalIntegrator helper classes instead of manually assembling the matrices.

The details of this framework on how it is used in practice will be explained as part of this tutorial program.

The testcase

The problem we solve here is the same as the one in step-16.

The commented program

Include files

Again, the first few include files are already known, so we won't comment on them:

These, now, are the include necessary for the multilevel methods. The first one declares how to handle Dirichlet boundary conditions on each of the levels of the multigrid method. For the actual description of the degrees of freedom, we do not need any new include file because DoFHandler already has all necessary methods implemented. We will only need to distribute the DoFs for the levels further down.

The rest of the include files deals with the mechanics of multigrid as a linear operator (solver or preconditioner).

Finally we include the MeshWorker framework. This framework through its function loop() and integration_loop(), automates loops over cells and assembling of data into vectors, matrices, etc. It obeys constraints automatically. Since we have to build several matrices and have to be aware of several sets of constraints, this will save us a lot of headache.

In order to save effort, we use the pre-implemented Laplacian found in

This is C++:

#include <iostream>
#include <fstream>
using namespace dealii;
namespace Step16
{

The integrator on each cell

The MeshWorker::integration_loop() expects a class that provides functions for integration on cells and boundary and interior faces. This is done by the following class. In the constructor, we tell the loop that cell integrals should be computed (the 'true'), but integrals should not be computed on boundary and interior faces (the two 'false'). Accordingly, we only need a cell function, but none for the faces.

template <int dim>
class LaplaceIntegrator : public MeshWorker::LocalIntegrator<dim>
{
public:
LaplaceIntegrator();
virtual void cell(MeshWorker::DoFInfo<dim> & dinfo,
MeshWorker::IntegrationInfo<dim> &info) const override;
};
template <int dim>
LaplaceIntegrator<dim>::LaplaceIntegrator()
: MeshWorker::LocalIntegrator<dim>(true, false, false)
{}

Next the actual integrator on each cell. We solve a Poisson problem with a coefficient one in the right half plane and one tenth in the left half plane.

The MeshWorker::LocalResults base class of MeshWorker::DoFInfo contains objects that can be filled in this local integrator. How many objects are created is determined inside the MeshWorker framework by the assembler class. Here, we test for instance that one matrix is required (MeshWorker::LocalResults::n_matrices()). The matrices are accessed through MeshWorker::LocalResults::matrix(), which takes the number of the matrix as its first argument. The second argument is only used for integrals over faces when there are two matrices for each test function used. Then, a second matrix with indicator 'true' would exist with the same index.

MeshWorker::IntegrationInfo provides one or several FEValues objects, which below are used by LocalIntegrators::Laplace::cell_matrix() or LocalIntegrators::L2::L2(). Since we are assembling only a single PDE, there is also only one of these objects with index zero.

In addition, we note that this integrator serves to compute the matrices for the multilevel preconditioner as well as the matrix and the right hand side for the global system. Since the assembler for a system requires an additional vector, MeshWorker::LocalResults::n_vectors() is returning a nonzero value. Accordingly, we fill a right hand side vector at the end of this function. Since LocalResults can deal with several BlockVector objects, but we are again in the simplest case here, we enter the information into block zero of vector zero.

template <int dim>
void
LaplaceIntegrator<dim>::cell(MeshWorker::DoFInfo<dim> & dinfo,
{
const double coefficient = (dinfo.cell->center()(0) > 0.) ? .1 : 1.;
info.fe_values(0),
coefficient);
if (dinfo.n_vectors() > 0)
{
std::vector<double> rhs(info.fe_values(0).n_quadrature_points, 1.);
info.fe_values(0),
rhs);
}
}

The LaplaceProblem class template

This main class is basically the same class as in step-6. As far as member functions is concerned, the only addition is the assemble_multigrid function that assembles the matrices that correspond to the discrete operators on intermediate levels:

template <int dim>
class LaplaceProblem
{
public:
LaplaceProblem(const unsigned int degree);
void run();
private:
void setup_system();
void assemble_system();
void assemble_multigrid();
void solve();
void refine_grid();
void output_results(const unsigned int cycle) const;
DoFHandler<dim> dof_handler;
SparsityPattern sparsity_pattern;
SparseMatrix<double> system_matrix;
Vector<double> solution;
Vector<double> system_rhs;
const unsigned int degree;

The following members are the essential data structures for the multigrid method. The first two represent the sparsity patterns and the matrices on individual levels of the multilevel hierarchy, very much like the objects for the global mesh above.

Then we have two new matrices only needed for multigrid methods with local smoothing on adaptive meshes. They convey data between the interior part of the refined region and the refinement edge, as outlined in detail in the multigrid paper.

The last object stores information about the boundary indices on each level and information about indices lying on a refinement edge between two different refinement levels. It thus serves a similar purpose as AffineConstraints, but on each level.

MGLevelObject<SparsityPattern> mg_sparsity_patterns;
MGConstrainedDoFs mg_constrained_dofs;
};

The LaplaceProblem class implementation

Just one short remark about the constructor of the Triangulation: by convention, all adaptively refined triangulations in deal.II never change by more than one level across a face between cells. For our multigrid algorithms, however, we need a slightly stricter guarantee, namely that the mesh also does not change by more than refinement level across vertices that might connect two cells. In other words, we must prevent the following situation:

This is achieved by passing the Triangulation::limit_level_difference_at_vertices flag to the constructor of the triangulation class.

template <int dim>
LaplaceProblem<dim>::LaplaceProblem(const unsigned int degree)
: triangulation(Triangulation<dim>::limit_level_difference_at_vertices)
, fe(degree)
, dof_handler(triangulation)
, degree(degree)
{}

LaplaceProblem::setup_system

In addition to just distributing the degrees of freedom in the DoFHandler, we do the same on each level. Then, we follow the same procedure as before to set up the system on the leaf mesh.

template <int dim>
void LaplaceProblem<dim>::setup_system()
{
dof_handler.distribute_dofs(fe);
dof_handler.distribute_mg_dofs();
deallog << " Number of degrees of freedom: " << dof_handler.n_dofs()
<< " (by level: ";
for (unsigned int level = 0; level < triangulation.n_levels(); ++level)
deallog << dof_handler.n_dofs(level)
<< (level == triangulation.n_levels() - 1 ? ")" : ", ");
deallog << std::endl;
DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
solution.reinit(dof_handler.n_dofs());
system_rhs.reinit(dof_handler.n_dofs());
constraints.clear();
DoFTools::make_hanging_node_constraints(dof_handler, constraints);
std::set<types::boundary_id> dirichlet_boundary_ids = {0};
Functions::ZeroFunction<dim> homogeneous_dirichlet_bc;
const std::map<types::boundary_id, const Function<dim> *>
dirichlet_boundary_functions = {
{types::boundary_id(0), &homogeneous_dirichlet_bc}};
dirichlet_boundary_functions,
constraints);
constraints.close();
constraints.condense(dsp);
sparsity_pattern.copy_from(dsp);
system_matrix.reinit(sparsity_pattern);

The multigrid constraints have to be initialized. They need to know about the boundary values as well, so we pass the dirichlet_boundary here as well.

mg_constrained_dofs.clear();
mg_constrained_dofs.initialize(dof_handler);
mg_constrained_dofs.make_zero_boundary_constraints(dof_handler,
dirichlet_boundary_ids);

Now for the things that concern the multigrid data structures. First, we resize the multilevel objects to hold matrices and sparsity patterns for every level. The coarse level is zero (this is mandatory right now but may change in a future revision). Note that these functions take a complete, inclusive range here (not a starting index and size), so the finest level is n_levels-1. We first have to resize the container holding the SparseMatrix classes, since they have to release their SparsityPattern before the can be destroyed upon resizing.

const unsigned int n_levels = triangulation.n_levels();
mg_interface_in.resize(0, n_levels - 1);
mg_interface_in.clear_elements();
mg_interface_out.resize(0, n_levels - 1);
mg_interface_out.clear_elements();
mg_matrices.resize(0, n_levels - 1);
mg_matrices.clear_elements();
mg_sparsity_patterns.resize(0, n_levels - 1);

Now, we have to provide a matrix on each level. To this end, we first use the MGTools::make_sparsity_pattern function to generate a preliminary compressed sparsity pattern on each level (see the Sparsity patterns module for more information on this topic) and then copy it over to the one we really want. The next step is to initialize both kinds of level matrices with these sparsity patterns.

It may be worth pointing out that the interface matrices only have entries for degrees of freedom that sit at or next to the interface between coarser and finer levels of the mesh. They are therefore even sparser than the matrices on the individual levels of our multigrid hierarchy. If we were more concerned about memory usage (and possibly the speed with which we can multiply with these matrices), we should use separate and different sparsity patterns for these two kinds of matrices.

for (unsigned int level = 0; level < n_levels; ++level)
{
dof_handler.n_dofs(level));
mg_sparsity_patterns[level].copy_from(dsp);
mg_matrices[level].reinit(mg_sparsity_patterns[level]);
mg_interface_in[level].reinit(mg_sparsity_patterns[level]);
mg_interface_out[level].reinit(mg_sparsity_patterns[level]);
}
}

LaplaceProblem::assemble_system

The following function assembles the linear system on the finest level of the mesh. Since we want to reuse the code here for the level assembling below, we use the local integrator class LaplaceIntegrator and leave the loops to the MeshWorker framework. Thus, this function first sets up the objects necessary for this framework, namely

  • a MeshWorker::IntegrationInfoBox object, which will provide all the required data in quadrature points on the cell. This object can be seen as an extension of FEValues, providing a lot more useful information,
  • a MeshWorker::DoFInfo object, which on the one hand side extends the functionality of cell iterators, but also provides space for return values in its base class LocalResults,
  • an assembler, in this case for the whole system. The term 'simple' here refers to the fact that the global system does not have a block structure,
  • the local integrator, which implements the actual forms.

After the loop has combined all of these into a matrix and a right hand side, there is one thing left to do: the assemblers leave matrix rows and columns of constrained degrees of freedom untouched. Therefore, we put a one on the diagonal to make the whole system well posed. The value one, or any fixed value has the advantage, that its effect on the spectrum of the matrix is easily understood. Since the corresponding eigenvectors form an invariant subspace, the value chosen does not affect the convergence of Krylov space solvers.

template <int dim>
void LaplaceProblem<dim>::assemble_system()
{
MappingQ1<dim> mapping;
UpdateFlags update_flags =
info_box.add_update_flags_all(update_flags);
info_box.initialize(fe, mapping);
MeshWorker::DoFInfo<dim> dof_info(dof_handler);
assembler;
assembler.initialize(constraints);
assembler.initialize(system_matrix, system_rhs);
LaplaceIntegrator<dim> matrix_integrator;
MeshWorker::integration_loop<dim, dim>(dof_handler.begin_active(),
dof_handler.end(),
dof_info,
info_box,
matrix_integrator,
assembler);
for (unsigned int i = 0; i < dof_handler.n_dofs(); ++i)
if (constraints.is_constrained(i))
system_matrix.set(i, i, 1.);
}

LaplaceProblem::assemble_multigrid

The next function is the one that builds the linear operators (matrices) that define the multigrid method on each level of the mesh. The integration core is the same as above, but the loop below will go over all existing cells instead of just the active ones, and the results must be entered into the correct level matrices. Fortunately, MeshWorker hides most of that from us, and thus the difference between this function and the previous lies only in the setup of the assembler and the different iterators in the loop. Also, fixing up the matrices in the end is a little more complicated.

template <int dim>
void LaplaceProblem<dim>::assemble_multigrid()
{
MappingQ1<dim> mapping;
UpdateFlags update_flags =
info_box.add_update_flags_all(update_flags);
info_box.initialize(fe, mapping);
MeshWorker::DoFInfo<dim> dof_info(dof_handler);
assembler.initialize(mg_constrained_dofs);
assembler.initialize(mg_matrices);
assembler.initialize_interfaces(mg_interface_in, mg_interface_out);
LaplaceIntegrator<dim> matrix_integrator;
MeshWorker::integration_loop<dim, dim>(dof_handler.begin_mg(),
dof_handler.end_mg(),
dof_info,
info_box,
matrix_integrator,
assembler);
const unsigned int nlevels = triangulation.n_levels();
for (unsigned int level = 0; level < nlevels; ++level)
{
for (unsigned int i = 0; i < dof_handler.n_dofs(level); ++i)
if (mg_constrained_dofs.is_boundary_index(level, i) ||
mg_constrained_dofs.at_refinement_edge(level, i))
mg_matrices[level].set(i, i, 1.);
}
}

LaplaceProblem::solve

This is the other function that is significantly different in support of the multigrid solver (or, in fact, the preconditioner for which we use the multigrid method).

Let us start out by setting up two of the components of multilevel methods: transfer operators between levels, and a solver on the coarsest level. In finite element methods, the transfer operators are derived from the finite element function spaces involved and can often be computed in a generic way independent of the problem under consideration. In that case, we can use the MGTransferPrebuilt class that, given the constraints of the final linear system and the MGConstrainedDoFs object that knows about the boundary conditions on the each level and the degrees of freedom on interfaces between different refinement level can build the matrices for those transfer operations from a DoFHandler object with level degrees of freedom.

The second part of the following lines deals with the coarse grid solver. Since our coarse grid is very coarse indeed, we decide for a direct solver (a Householder decomposition of the coarsest level matrix), even if its implementation is not particularly sophisticated. If our coarse mesh had many more cells than the five we have here, something better suited would obviously be necessary here.

template <int dim>
void LaplaceProblem<dim>::solve()
{
MGTransferPrebuilt<Vector<double>> mg_transfer(mg_constrained_dofs);
mg_transfer.build(dof_handler);
FullMatrix<double> coarse_matrix;
coarse_matrix.copy_from(mg_matrices[0]);
coarse_grid_solver.initialize(coarse_matrix);

The next component of a multilevel solver or preconditioner is that we need a smoother on each level. A common choice for this is to use the application of a relaxation method (such as the SOR, Jacobi or Richardson method) or a small number of iterations of a solver method (such as CG or GMRES). The mg::SmootherRelaxation and MGSmootherPrecondition classes provide support for these two kinds of smoothers. Here, we opt for the application of a single SOR iteration. To this end, we define an appropriate alias and then setup a smoother object.

The last step is to initialize the smoother object with our level matrices and to set some smoothing parameters. The initialize() function can optionally take additional arguments that will be passed to the smoother object on each level. In the current case for the SOR smoother, this could, for example, include a relaxation parameter. However, we here leave these at their default values. The call to set_steps() indicates that we will use two pre- and two post-smoothing steps on each level; to use a variable number of smoother steps on different levels, more options can be set in the constructor call to the mg_smoother object.

The last step results from the fact that we use the SOR method as a smoother - which is not symmetric - but we use the conjugate gradient iteration (which requires a symmetric preconditioner) below, we need to let the multilevel preconditioner make sure that we get a symmetric operator even for nonsymmetric smoothers:

mg_smoother.initialize(mg_matrices);
mg_smoother.set_steps(2);
mg_smoother.set_symmetric(true);

The next preparatory step is that we must wrap our level and interface matrices in an object having the required multiplication functions. We will create two objects for the interface objects going from coarse to fine and the other way around; the multigrid algorithm will later use the transpose operator for the latter operation, allowing us to initialize both up and down versions of the operator with the matrices we already built:

mg::Matrix<Vector<double>> mg_matrix(mg_matrices);
mg::Matrix<Vector<double>> mg_interface_up(mg_interface_in);
mg::Matrix<Vector<double>> mg_interface_down(mg_interface_out);

Now, we are ready to set up the V-cycle operator and the multilevel preconditioner.

mg_matrix, coarse_grid_solver, mg_transfer, mg_smoother, mg_smoother);
mg.set_edge_matrices(mg_interface_down, mg_interface_up);
preconditioner(dof_handler, mg, mg_transfer);

With all this together, we can finally get about solving the linear system in the usual way:

SolverControl solver_control(1000, 1e-12);
SolverCG<Vector<double>> solver(solver_control);
solution = 0;
solver.solve(system_matrix, solution, system_rhs, preconditioner);
constraints.distribute(solution);
}

Postprocessing

The following two functions postprocess a solution once it is computed. In particular, the first one refines the mesh at the beginning of each cycle while the second one outputs results at the end of each such cycle. The functions are almost unchanged from those in step-6, with the exception of one minor difference: we generate output in VTK format, to use the more modern visualization programs available today compared to those that were available when step-6 was written.

template <int dim>
void LaplaceProblem<dim>::refine_grid()
{
Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
dof_handler,
std::map<types::boundary_id, const Function<dim> *>(),
solution,
estimated_error_per_cell);
estimated_error_per_cell,
0.3,
0.03);
triangulation.execute_coarsening_and_refinement();
}
template <int dim>
void LaplaceProblem<dim>::output_results(const unsigned int cycle) const
{
DataOut<dim> data_out;
data_out.attach_dof_handler(dof_handler);
data_out.add_data_vector(solution, "solution");
data_out.build_patches();
std::ofstream output("solution-" + std::to_string(cycle) + ".vtk");
data_out.write_vtk(output);
}

LaplaceProblem::run

Like several of the functions above, this is almost exactly a copy of the corresponding function in step-6. The only difference is the call to assemble_multigrid that takes care of forming the matrices on every level that we need in the multigrid method.

template <int dim>
{
for (unsigned int cycle = 0; cycle < 8; ++cycle)
{
deallog << "Cycle " << cycle << std::endl;
if (cycle == 0)
{
triangulation.refine_global(1);
}
else
refine_grid();
deallog << " Number of active cells: "
<< triangulation.n_active_cells() << std::endl;
setup_system();
assemble_system();
assemble_multigrid();
solve();
output_results(cycle);
}
}
} // namespace Step16

The main() function

This is again the same function as in step-6:

int main()
{
try
{
using namespace Step16;
LaplaceProblem<2> laplace_problem(1);
laplace_problem.run();
}
catch (std::exception &exc)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Exception on processing: " << std::endl
<< exc.what() << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
catch (...)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Unknown exception!" << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
return 0;
}

Results

As in step-16, the solution looks like this on the finest mesh:

The output is formatted in a slightly different way compared to step-16 but is functionally the same and shows the same convergence properties:

DEAL::Cycle 0
DEAL::   Number of active cells:       20
DEAL::   Number of degrees of freedom: 25 (by level: 8, 25)
DEAL:cg::Starting value 0.510691
DEAL:cg::Convergence step 6 value 4.59193e-14
DEAL::Cycle 1
DEAL::   Number of active cells:       44
DEAL::   Number of degrees of freedom: 55 (by level: 8, 25, 45)
DEAL:cg::Starting value 0.440678
DEAL:cg::Convergence step 8 value 1.99419e-13
DEAL::Cycle 2
DEAL::   Number of active cells:       86
DEAL::   Number of degrees of freedom: 105 (by level: 8, 25, 69, 49)
DEAL:cg::Starting value 0.371855
DEAL:cg::Convergence step 9 value 1.13984e-13
DEAL::Cycle 3
DEAL::   Number of active cells:       170
DEAL::   Number of degrees of freedom: 200 (by level: 8, 25, 77, 174)
DEAL:cg::Starting value 0.318967
DEAL:cg::Convergence step 9 value 2.62112e-13
DEAL::Cycle 4
DEAL::   Number of active cells:       332
DEAL::   Number of degrees of freedom: 388 (by level: 8, 25, 86, 231, 204)
DEAL:cg::Starting value 0.276534
DEAL:cg::Convergence step 10 value 1.69562e-13
DEAL::Cycle 5
DEAL::   Number of active cells:       632
DEAL::   Number of degrees of freedom: 714 (by level: 8, 25, 89, 231, 514, 141)
DEAL:cg::Starting value 0.215300
DEAL:cg::Convergence step 10 value 6.47463e-13
DEAL::Cycle 6
DEAL::   Number of active cells:       1202
DEAL::   Number of degrees of freedom: 1332 (by level: 8, 25, 89, 282, 771, 435, 257)
DEAL:cg::Starting value 0.175848
DEAL:cg::Convergence step 10 value 1.80664e-13
DEAL::Cycle 7
DEAL::   Number of active cells:       2288
DEAL::   Number of degrees of freedom: 2511 (by level: 8, 25, 89, 318, 779, 1420, 829, 30)
DEAL:cg::Starting value 0.136724
DEAL:cg::Convergence step 11 value 9.73331e-14

The plain program

/* ---------------------------------------------------------------------
*
* Copyright (C) 2003 - 2020 by the deal.II authors
*
* This file is part of the deal.II library.
*
* The deal.II library is free software; you can use it, redistribute
* it, and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* The full text of the license can be found in the file LICENSE.md at
* the top level directory of deal.II.
*
* ---------------------------------------------------------------------
*
* Authors: Guido Kanschat, University of Heidelberg, 2003
* Baerbel Janssen, University of Heidelberg, 2010
* Wolfgang Bangerth, Texas A&M University, 2010
*/
#include <iostream>
#include <fstream>
using namespace dealii;
namespace Step16
{
template <int dim>
class LaplaceIntegrator : public MeshWorker::LocalIntegrator<dim>
{
public:
LaplaceIntegrator();
virtual void cell(MeshWorker::DoFInfo<dim> & dinfo,
MeshWorker::IntegrationInfo<dim> &info) const override;
};
template <int dim>
LaplaceIntegrator<dim>::LaplaceIntegrator()
: MeshWorker::LocalIntegrator<dim>(true, false, false)
{}
template <int dim>
void
LaplaceIntegrator<dim>::cell(MeshWorker::DoFInfo<dim> & dinfo,
{
const double coefficient = (dinfo.cell->center()(0) > 0.) ? .1 : 1.;
info.fe_values(0),
coefficient);
if (dinfo.n_vectors() > 0)
{
std::vector<double> rhs(info.fe_values(0).n_quadrature_points, 1.);
info.fe_values(0),
rhs);
}
}
template <int dim>
class LaplaceProblem
{
public:
LaplaceProblem(const unsigned int degree);
void run();
private:
void setup_system();
void assemble_system();
void assemble_multigrid();
void solve();
void refine_grid();
void output_results(const unsigned int cycle) const;
DoFHandler<dim> dof_handler;
SparsityPattern sparsity_pattern;
SparseMatrix<double> system_matrix;
Vector<double> solution;
Vector<double> system_rhs;
const unsigned int degree;
MGLevelObject<SparsityPattern> mg_sparsity_patterns;
MGConstrainedDoFs mg_constrained_dofs;
};
template <int dim>
LaplaceProblem<dim>::LaplaceProblem(const unsigned int degree)
: triangulation(Triangulation<dim>::limit_level_difference_at_vertices)
, fe(degree)
, dof_handler(triangulation)
, degree(degree)
{}
template <int dim>
void LaplaceProblem<dim>::setup_system()
{
dof_handler.distribute_dofs(fe);
dof_handler.distribute_mg_dofs();
deallog << " Number of degrees of freedom: " << dof_handler.n_dofs()
<< " (by level: ";
for (unsigned int level = 0; level < triangulation.n_levels(); ++level)
deallog << dof_handler.n_dofs(level)
<< (level == triangulation.n_levels() - 1 ? ")" : ", ");
deallog << std::endl;
DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
solution.reinit(dof_handler.n_dofs());
system_rhs.reinit(dof_handler.n_dofs());
constraints.clear();
DoFTools::make_hanging_node_constraints(dof_handler, constraints);
std::set<types::boundary_id> dirichlet_boundary_ids = {0};
Functions::ZeroFunction<dim> homogeneous_dirichlet_bc;
const std::map<types::boundary_id, const Function<dim> *>
dirichlet_boundary_functions = {
{types::boundary_id(0), &homogeneous_dirichlet_bc}};
dirichlet_boundary_functions,
constraints);
constraints.close();
constraints.condense(dsp);
sparsity_pattern.copy_from(dsp);
system_matrix.reinit(sparsity_pattern);
mg_constrained_dofs.clear();
mg_constrained_dofs.initialize(dof_handler);
mg_constrained_dofs.make_zero_boundary_constraints(dof_handler,
dirichlet_boundary_ids);
const unsigned int n_levels = triangulation.n_levels();
mg_interface_in.resize(0, n_levels - 1);
mg_interface_in.clear_elements();
mg_interface_out.resize(0, n_levels - 1);
mg_interface_out.clear_elements();
mg_matrices.resize(0, n_levels - 1);
mg_matrices.clear_elements();
mg_sparsity_patterns.resize(0, n_levels - 1);
for (unsigned int level = 0; level < n_levels; ++level)
{
dof_handler.n_dofs(level));
mg_sparsity_patterns[level].copy_from(dsp);
mg_matrices[level].reinit(mg_sparsity_patterns[level]);
mg_interface_in[level].reinit(mg_sparsity_patterns[level]);
mg_interface_out[level].reinit(mg_sparsity_patterns[level]);
}
}
template <int dim>
void LaplaceProblem<dim>::assemble_system()
{
MappingQ1<dim> mapping;
UpdateFlags update_flags =
info_box.add_update_flags_all(update_flags);
info_box.initialize(fe, mapping);
MeshWorker::DoFInfo<dim> dof_info(dof_handler);
assembler;
assembler.initialize(constraints);
assembler.initialize(system_matrix, system_rhs);
LaplaceIntegrator<dim> matrix_integrator;
MeshWorker::integration_loop<dim, dim>(dof_handler.begin_active(),
dof_handler.end(),
dof_info,
info_box,
matrix_integrator,
assembler);
for (unsigned int i = 0; i < dof_handler.n_dofs(); ++i)
if (constraints.is_constrained(i))
system_matrix.set(i, i, 1.);
}
template <int dim>
void LaplaceProblem<dim>::assemble_multigrid()
{
MappingQ1<dim> mapping;
UpdateFlags update_flags =
info_box.add_update_flags_all(update_flags);
info_box.initialize(fe, mapping);
MeshWorker::DoFInfo<dim> dof_info(dof_handler);
assembler.initialize(mg_constrained_dofs);
assembler.initialize(mg_matrices);
assembler.initialize_interfaces(mg_interface_in, mg_interface_out);
LaplaceIntegrator<dim> matrix_integrator;
MeshWorker::integration_loop<dim, dim>(dof_handler.begin_mg(),
dof_handler.end_mg(),
dof_info,
info_box,
matrix_integrator,
assembler);
const unsigned int nlevels = triangulation.n_levels();
for (unsigned int level = 0; level < nlevels; ++level)
{
for (unsigned int i = 0; i < dof_handler.n_dofs(level); ++i)
if (mg_constrained_dofs.is_boundary_index(level, i) ||
mg_constrained_dofs.at_refinement_edge(level, i))
mg_matrices[level].set(i, i, 1.);
}
}
template <int dim>
void LaplaceProblem<dim>::solve()
{
MGTransferPrebuilt<Vector<double>> mg_transfer(mg_constrained_dofs);
mg_transfer.build(dof_handler);
FullMatrix<double> coarse_matrix;
coarse_matrix.copy_from(mg_matrices[0]);
coarse_grid_solver.initialize(coarse_matrix);
mg_smoother.initialize(mg_matrices);
mg_smoother.set_steps(2);
mg_smoother.set_symmetric(true);
mg::Matrix<Vector<double>> mg_matrix(mg_matrices);
mg::Matrix<Vector<double>> mg_interface_up(mg_interface_in);
mg::Matrix<Vector<double>> mg_interface_down(mg_interface_out);
mg_matrix, coarse_grid_solver, mg_transfer, mg_smoother, mg_smoother);
mg.set_edge_matrices(mg_interface_down, mg_interface_up);
preconditioner(dof_handler, mg, mg_transfer);
SolverControl solver_control(1000, 1e-12);
SolverCG<Vector<double>> solver(solver_control);
solution = 0;
solver.solve(system_matrix, solution, system_rhs, preconditioner);
constraints.distribute(solution);
}
template <int dim>
void LaplaceProblem<dim>::refine_grid()
{
Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
dof_handler,
std::map<types::boundary_id, const Function<dim> *>(),
solution,
estimated_error_per_cell);
estimated_error_per_cell,
0.3,
0.03);
triangulation.execute_coarsening_and_refinement();
}
template <int dim>
void LaplaceProblem<dim>::output_results(const unsigned int cycle) const
{
DataOut<dim> data_out;
data_out.attach_dof_handler(dof_handler);
data_out.add_data_vector(solution, "solution");
data_out.build_patches();
std::ofstream output("solution-" + std::to_string(cycle) + ".vtk");
data_out.write_vtk(output);
}
template <int dim>
{
for (unsigned int cycle = 0; cycle < 8; ++cycle)
{
deallog << "Cycle " << cycle << std::endl;
if (cycle == 0)
{
triangulation.refine_global(1);
}
else
refine_grid();
deallog << " Number of active cells: "
<< triangulation.n_active_cells() << std::endl;
setup_system();
assemble_system();
assemble_multigrid();
solve();
output_results(cycle);
}
}
} // namespace Step16
int main()
{
try
{
using namespace Step16;
LaplaceProblem<2> laplace_problem(1);
laplace_problem.run();
}
catch (std::exception &exc)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Exception on processing: " << std::endl
<< exc.what() << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
catch (...)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Unknown exception!" << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
return 0;
}
MeshWorker::IntegrationInfoBox::initialize
void initialize(const FiniteElement< dim, spacedim > &el, const Mapping< dim, spacedim > &mapping, const BlockInfo *block_info=nullptr)
fe_values.h
sparse_matrix.h
tria_accessor.h
SolverCG
Definition: solver_cg.h:98
FullMatrix::copy_from
void copy_from(const MatrixType &)
mg_smoother.h
FE_Q
Definition: fe_q.h:554
DoFHandler::begin_mg
level_cell_iterator begin_mg(const unsigned int level=0) const
Definition: dof_handler.cc:984
dealii
Definition: namespace_dealii.h:25
mg_coarse.h
MeshWorker::LocalResults< double >::n_matrices
unsigned int n_matrices() const
Definition: local_results.h:552
MeshWorker::Assembler::SystemSimple
Definition: simple.h:457
MGConstrainedDoFs
Definition: mg_constrained_dofs.h:45
Triangulation< dim >
tria.h
MeshWorker
Definition: assemble_flags.h:30
multigrid.h
GridRefinement::refine_and_coarsen_fixed_number
void refine_and_coarsen_fixed_number(Triangulation< dim, spacedim > &triangulation, const Vector< Number > &criteria, const double top_fraction_of_cells, const double bottom_fraction_of_cells, const unsigned int max_n_cells=std::numeric_limits< unsigned int >::max())
Definition: grid_refinement.cc:189
utilities.h
tria_iterator.h
LocalIntegrators::Laplace::cell_matrix
void cell_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const double factor=1.)
Definition: laplace.h:57
MeshWorker::IntegrationInfo::fe_values
const FEValuesBase< dim, spacedim > & fe_values() const
Access to finite element.
Definition: integration_info.h:678
SparseMatrix< double >
Patterns::Tools::to_string
std::string to_string(const T &t)
Definition: patterns.h:2360
MGTools::make_sparsity_pattern
void make_sparsity_pattern(const DoFHandlerType &dof_handler, SparsityPatternType &sparsity, const unsigned int level)
Definition: mg_tools.cc:565
Physics::Elasticity::Kinematics::e
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
l2.h
mg::SmootherRelaxation
Definition: mg_smoother.h:190
types::boundary_id
unsigned int boundary_id
Definition: types.h:129
mg
Definition: mg.h:81
DataOutInterface::write_vtk
void write_vtk(std::ostream &out) const
Definition: data_out_base.cc:6853
update_values
@ update_values
Shape function values.
Definition: fe_update_flags.h:78
integration_info.h
DataOut::build_patches
virtual void build_patches(const unsigned int n_subdivisions=0)
Definition: data_out.cc:1071
mg_matrix.h
MeshWorker::IntegrationInfo
Definition: integration_info.h:78
Multigrid
Definition: multigrid.h:137
DoFHandler< dim >
quadrature_lib.h
deallog
LogStream deallog
Definition: logstream.cc:37
DoFHandler::distribute_dofs
virtual void distribute_dofs(const FiniteElement< dim, spacedim > &fe)
Definition: dof_handler.cc:1247
precondition.h
MeshWorker::LocalIntegrator::cell
virtual void cell(DoFInfo< dim, spacedim, number > &dinfo, IntegrationInfo< dim, spacedim > &info) const
Definition: mesh_worker.cc:81
FiniteElementData::degree
const unsigned int degree
Definition: fe_base.h:298
WorkStream::run
void run(const std::vector< std::vector< Iterator >> &colored_iterators, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
Definition: work_stream.h:1185
level
unsigned int level
Definition: grid_out.cc:4355
MGSmoother::set_symmetric
void set_symmetric(const bool)
simple.h
PreconditionSOR
Definition: precondition.h:596
MeshWorker::LocalIntegrator
Definition: local_integrator.h:58
mg::SmootherRelaxation::initialize
void initialize(const MGLevelObject< MatrixType2 > &matrices, const typename RelaxationType::AdditionalData &additional_data=typename RelaxationType::AdditionalData())
DoFTools::make_sparsity_pattern
void make_sparsity_pattern(const DoFHandlerType &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)
Definition: dof_tools_sparsity.cc:63
VectorTools::interpolate_boundary_values
void interpolate_boundary_values(const Mapping< dim, spacedim > &mapping, const DoFHandlerType< dim, spacedim > &dof, const std::map< types::boundary_id, const Function< spacedim, number > * > &function_map, std::map< types::global_dof_index, number > &boundary_values, const ComponentMask &component_mask=ComponentMask())
mg_tools.h
MeshWorker::LocalResults< double >::n_vectors
unsigned int n_vectors() const
Definition: local_results.h:544
MeshWorker::Assembler::MGMatrixSimple::initialize
void initialize(MGLevelObject< MatrixType > &m)
Definition: simple.h:790
MGSmoother::set_steps
void set_steps(const unsigned int)
MappingQ1
Definition: mapping_q1.h:57
mg::Matrix
Definition: mg_matrix.h:47
update_gradients
@ update_gradients
Shape function gradients.
Definition: fe_update_flags.h:84
MeshWorker::LocalResults< double >::vector
BlockVector< double > & vector(const unsigned int i)
Definition: local_results.h:585
KellyErrorEstimator::estimate
static void estimate(const Mapping< dim, spacedim > &mapping, const DoFHandlerType &dof, const Quadrature< dim - 1 > &quadrature, const std::map< types::boundary_id, const Function< spacedim, typename InputVector::value_type > * > &neumann_bc, const InputVector &solution, Vector< float > &error, const ComponentMask &component_mask=ComponentMask(), const Function< spacedim > *coefficients=nullptr, const unsigned int n_threads=numbers::invalid_unsigned_int, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id, const types::material_id material_id=numbers::invalid_material_id, const Strategy strategy=cell_diameter_over_24)
error_estimator.h
laplace.h
MeshWorker::Assembler::SystemSimple::initialize
void initialize(MatrixType &m, VectorType &rhs)
Definition: simple.h:1240
DynamicSparsityPattern
Definition: dynamic_sparsity_pattern.h:323
SparsityPattern
Definition: sparsity_pattern.h:865
MGTransferPrebuilt
Definition: mg_transfer.h:671
AssertDimension
#define AssertDimension(dim1, dim2)
Definition: exceptions.h:1579
MeshWorker::LocalResults< double >::matrix
MatrixBlock< FullMatrix< double > > & matrix(const unsigned int i, const bool external=false)
Definition: local_results.h:594
fe_q.h
DoFTools::make_hanging_node_constraints
void make_hanging_node_constraints(const DoFHandlerType &dof_handler, AffineConstraints< number > &constraints)
Definition: dof_tools_constraints.cc:1725
dof_info.h
grid_refinement.h
AffineConstraints::condense
void condense(SparsityPattern &sparsity) const
Vector::reinit
virtual void reinit(const size_type N, const bool omit_zeroing_entries=false)
UpdateFlags
UpdateFlags
Definition: fe_update_flags.h:66
QGauss
Definition: quadrature_lib.h:40
DoFHandler::end
cell_iterator end() const
Definition: dof_handler.cc:951
mg_constrained_dofs.h
LocalIntegrators::L2::L2
void L2(Vector< number > &result, const FEValuesBase< dim > &fe, const std::vector< double > &input, const double factor=1.)
Definition: l2.h:171
MGCoarseGridHouseholder
Definition: mg_coarse.h:265
DataOut_DoFData::attach_dof_handler
void attach_dof_handler(const DoFHandlerType &)
LogStream::depth_console
unsigned int depth_console(const unsigned int n)
Definition: logstream.cc:349
unsigned int
GridGenerator::hyper_ball
void hyper_ball(Triangulation< dim > &tria, const Point< dim > &center=Point< dim >(), const double radius=1., const bool attach_spherical_manifold_on_boundary_cells=false)
AffineConstraints< double >
AffineConstraints::distribute
void distribute(VectorType &vec) const
dof_tools.h
function.h
solver_cg.h
AffineConstraints::clear
void clear()
MeshWorker::Assembler::MGMatrixSimple
Definition: simple.h:273
update_hessians
@ update_hessians
Second derivatives of shape functions.
Definition: fe_update_flags.h:90
PreconditionMG
Definition: multigrid.h:454
MGLevelObject< SparsityPattern >
loop.h
vector.h
vector_tools.h
MeshWorker::IntegrationInfoBox
Definition: integration_info.h:299
DoFHandler::end_mg
level_cell_iterator end_mg(const unsigned int level) const
Definition: dof_handler.cc:998
MeshWorker::IntegrationInfoBox::add_update_flags_all
void add_update_flags_all(const UpdateFlags flags)
Definition: integration_info.h:753
dof_accessor.h
affine_constraints.h
grid_generator.h
Functions::ZeroFunction
Definition: function.h:513
FEValuesBase::n_quadrature_points
const unsigned int n_quadrature_points
Definition: fe_values.h:2101
DoFHandler::distribute_mg_dofs
virtual void distribute_mg_dofs()
Definition: dof_handler.cc:1296
FullMatrix< double >
Function< dim >
triangulation
const typename ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
Definition: p4est_wrappers.cc:69
MGCoarseGridHouseholder::initialize
void initialize(const FullMatrix< number > &A)
SolverControl
Definition: solver_control.h:67
BlockVectorBase::block
BlockType & block(const unsigned int i)
logstream.h
data_out.h
mg_transfer.h
DataOut< dim >
DoFHandler::begin_active
active_cell_iterator begin_active(const unsigned int level=0) const
Definition: dof_handler.cc:935
MeshWorker::Assembler::MGMatrixSimple::initialize_interfaces
void initialize_interfaces(MGLevelObject< MatrixType > &interface_in, MGLevelObject< MatrixType > &interface_out)
Definition: simple.h:816
Vector< double >
MatrixBlock::matrix
MatrixType matrix
Definition: matrix_block.h:317
AffineConstraints::close
void close()
MeshWorker::DoFInfo
Definition: dof_info.h:76
full_matrix.h
output.h
MeshWorker::DoFInfo::cell
Triangulation< dim, spacedim >::cell_iterator cell
The current cell.
Definition: dof_info.h:80
DoFHandler::n_dofs
types::global_dof_index n_dofs() const
DataOut_DoFData::add_data_vector
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=std::vector< DataComponentInterpretation::DataComponentInterpretation >())
Definition: data_out_dof_data.h:1090
AffineConstraints::is_constrained
bool is_constrained(const size_type line_n) const
Definition: affine_constraints.h:1722