470 *
const std::array<double, 2> polar =
473 *
constexpr const double alpha = 2. / 3.;
483 * <a name=
"step_75-Parameters"></a>
484 * <h3>Parameters</h3>
488 * For
this tutorial, we will use a simplified
set of parameters. It is also
490 *
short we decided on
using simple structs. The actual intention of all these
491 * parameters will be described in the upcoming classes at their respective
492 * location where they are used.
496 * The following parameter
set controls the coarse-grid solver, the smoothers,
497 * and the inter-grid transfer scheme of the multigrid mechanism.
498 * We populate it with
default parameters.
501 *
struct MultigridParameters
505 *
std::string type =
"cg_with_amg";
506 *
unsigned int maxiter = 10000;
507 *
double abstol = 1
e-20;
508 *
double reltol = 1
e-4;
509 *
unsigned int smoother_sweeps = 1;
510 *
unsigned int n_cycles = 1;
511 *
std::string smoother_type =
"ILU";
516 *
std::string type =
"chebyshev";
517 *
double smoothing_range = 20;
518 *
unsigned int degree = 5;
519 *
unsigned int eig_cg_n_iterations = 20;
526 *
PolynomialCoarseningSequenceType::decrease_by_one;
527 *
bool perform_h_transfer =
true;
535 * This is the
general parameter
struct for the problem class. You will find
536 *
this struct divided into several categories, including
general runtime
538 * parameters
for cell weighting. It also contains an instance of the above
539 *
struct for multigrid parameters which will be passed to the multigrid
545 *
unsigned int n_cycles = 8;
546 *
double tolerance_factor = 1
e-12;
548 *
MultigridParameters mg_data;
550 *
unsigned int min_h_level = 5;
551 *
unsigned int max_h_level = 12;
552 *
unsigned int min_p_degree = 2;
553 *
unsigned int max_p_degree = 6;
554 *
unsigned int max_p_level_difference = 1;
556 *
double refine_fraction = 0.3;
557 *
double coarsen_fraction = 0.03;
558 *
double p_refine_fraction = 0.9;
559 *
double p_coarsen_fraction = 0.9;
561 *
double weighting_factor = 1.;
562 *
double weighting_exponent = 1.;
570 * <a name=
"step_75-MatrixfreeLaplaceoperator"></a>
571 * <h3>Matrix-free Laplace
operator</h3>
575 * This is a
matrix-free implementation of the Laplace
operator that will
576 * basically take over the part of the `assemble_system()` function from other
577 * tutorials. The meaning of all member
functions will be explained at their
582 * We will use the
FEEvaluation class to evaluate the solution vector
583 * at the quadrature points and to perform the integration. In contrast to
584 * other tutorials, the
template arguments `degree` is
set to @f$-1@f$ and
585 * `number of quadrature in 1
d` to @f$0@f$. In
this case,
FEEvaluation selects
586 * dynamically the correct polynomial degree and number of quadrature
587 * points. Here, we introduce an alias to
FEEvaluation with the correct
588 *
template parameters so that we
do not have to worry about them later on.
591 *
template <
int dim,
typename number>
597 *
using FECellIntegrator =
FEEvaluation<dim, -1, 0, 1, number>;
599 *
LaplaceOperator() =
default;
605 *
VectorType &system_rhs);
611 *
VectorType &system_rhs);
615 *
number el(
unsigned int,
unsigned int)
const;
617 *
void initialize_dof_vector(VectorType &vec)
const;
619 *
void vmult(VectorType &dst,
const VectorType &src)
const;
621 *
void Tvmult(VectorType &dst,
const VectorType &src)
const;
625 *
void compute_inverse_diagonal(VectorType &diagonal)
const;
628 *
void do_cell_integral_local(FECellIntegrator &integrator)
const;
630 *
void do_cell_integral_global(FECellIntegrator &integrator,
632 *
const VectorType &src)
const;
635 *
void do_cell_integral_range(
638 *
const VectorType &src,
639 *
const std::pair<unsigned int, unsigned int> &range)
const;
645 * To solve the equation system on the coarsest
level with an AMG
646 * preconditioner, we need an actual system
matrix on the coarsest
level.
647 * For
this purpose, we provide a mechanism that optionally computes a
648 *
matrix from the
matrix-free formulation,
for which we introduce a
650 * empty. Once `get_system_matrix()` is called,
this matrix is filled (lazy
651 * allocation). Since
this is a `
const` function, we need the
"mutable"
652 * keyword here. We also need a the constraints
object to build the
matrix.
663 * The following section contains
functions to initialize and reinitialize
665 *
MatrixFree instance. For sake of simplicity, we also compute the system
666 * right-hand-side vector.
669 *
template <
int dim,
typename number>
670 *
LaplaceOperator<dim, number>::LaplaceOperator(
675 *
VectorType &system_rhs)
677 *
this->
reinit(mapping, dof_handler, quad, constraints, system_rhs);
682 *
template <
int dim,
typename number>
683 *
void LaplaceOperator<dim, number>::reinit(
688 *
VectorType &system_rhs)
692 * Clear
internal data structures (in the
case that the
operator is reused).
695 *
this->system_matrix.clear();
699 * Copy the constraints, since they might be needed
for computation of the
703 *
this->constraints.copy_from(constraints);
707 * Set up
MatrixFree. At the quadrature points, we only need to evaluate
715 *
matrix_free.reinit(mapping, dof_handler, constraints, quad,
data);
719 * Compute the right-hand side vector. For
this purpose, we
set up a
second
721 * the constraints due to Dirichlet-boundary conditions. This modified
722 *
operator is applied to a vector with only the Dirichlet
values set. The
723 * result is the
negative right-hand-side vector.
728 *
dof_handler.locally_owned_dofs(),
732 *
constraints_without_dbc);
733 *
constraints_without_dbc.close();
737 *
this->initialize_dof_vector(system_rhs);
741 *
mapping, dof_handler, constraints_without_dbc, quad,
data);
743 *
matrix_free.initialize_dof_vector(b);
744 *
matrix_free.initialize_dof_vector(x);
746 *
constraints.distribute(x);
748 *
matrix_free.cell_loop(&LaplaceOperator::do_cell_integral_range,
753 *
constraints.set_zero(b);
763 * The following
functions are implicitly needed by the multigrid algorithm,
764 * including the smoothers.
769 * degrees of freedom.
772 *
template <
int dim,
typename number>
775 *
return matrix_free.get_dof_handler().n_dofs();
782 * Access a particular element in the
matrix. This function is neither
783 * needed nor implemented, however, is required to compile the program.
786 *
template <
int dim,
typename number>
787 *
number LaplaceOperator<dim, number>::el(
unsigned int,
unsigned int)
const
797 * Initialize the given vector. We simply delegate the task to the
801 *
template <
int dim,
typename number>
803 *
LaplaceOperator<dim, number>::initialize_dof_vector(VectorType &vec)
const
812 * Perform an
operator evaluation by looping with the help of
MatrixFree
813 * over all cells and evaluating the effect of the cell integrals (see also:
814 * `do_cell_integral_local()` and `do_cell_integral_global()`).
817 *
template <
int dim, typename number>
818 *
void LaplaceOperator<dim, number>::vmult(
VectorType &dst,
821 *
this->matrix_free.cell_loop(
822 *
&LaplaceOperator::do_cell_integral_range, this, dst, src, true);
829 * Perform the transposed
operator evaluation. Since we are considering
830 *
symmetric "matrices",
this function can simply delegate it task to vmult().
833 *
template <
int dim,
typename number>
834 *
void LaplaceOperator<dim, number>::Tvmult(VectorType &dst,
835 *
const VectorType &src)
const
837 *
this->vmult(dst, src);
844 * Since we
do not have a system
matrix, we cannot
loop over the the
846 * performing a sequence of
operator evaluations to unit basis vectors.
848 *
namespace is used. The inversion is performed manually afterwards.
851 *
template <
int dim,
typename number>
852 *
void LaplaceOperator<dim, number>::compute_inverse_diagonal(
853 *
VectorType &diagonal)
const
855 *
this->matrix_free.initialize_dof_vector(diagonal);
858 *
&LaplaceOperator::do_cell_integral_local,
862 *
i = (
std::
abs(i) > 1.0e-10) ? (1.0 / i) : 1.0;
870 * initialization of
this class. As a consequence, it has to be computed
871 * here
if it should be requested. Since the
matrix is only computed in
872 *
this tutorial
for linear elements (on the coarse grid),
this is
874 * The
matrix entries are obtained via sequence of
operator evaluations.
876 * is used. The
matrix will only be computed
if it has not been
set up yet
880 *
template <int dim, typename number>
882 *
LaplaceOperator<dim, number>::get_system_matrix() const
884 *
if (system_matrix.m() == 0 && system_matrix.n() == 0)
886 *
const auto &dof_handler = this->matrix_free.get_dof_handler();
888 *
const IndexSet locally_relevant_dofs =
895 *
dof_handler.locally_owned_dofs(),
896 *
dof_handler.get_triangulation().get_mpi_communicator(),
897 *
locally_relevant_dofs);
899 *
system_matrix.reinit(dsp);
905 *
&LaplaceOperator::do_cell_integral_local,
909 *
return this->system_matrix;
916 * Perform cell integral on a cell batch without gathering and scattering
921 *
template <
int dim,
typename number>
922 *
void LaplaceOperator<dim, number>::do_cell_integral_local(
923 *
FECellIntegrator &integrator)
const
927 *
for (
const unsigned int q : integrator.quadrature_point_indices())
928 *
integrator.submit_gradient(integrator.get_gradient(q), q);
937 * Same as above but with access to the global vectors.
940 *
template <
int dim,
typename number>
941 *
void LaplaceOperator<dim, number>::do_cell_integral_global(
942 *
FECellIntegrator &integrator,
944 *
const VectorType &src)
const
948 *
for (
const unsigned int q : integrator.quadrature_point_indices())
949 *
integrator.submit_gradient(integrator.get_gradient(q), q);
958 * This function loops over all cell batches within a cell-batch range and
959 * calls the above function.
962 *
template <
int dim,
typename number>
963 *
void LaplaceOperator<dim, number>::do_cell_integral_range(
966 *
const VectorType &src,
967 *
const std::pair<unsigned int, unsigned int> &range)
const
969 *
FECellIntegrator integrator(matrix_free, range);
971 *
for (
unsigned cell = range.first; cell < range.second; ++cell)
973 *
integrator.reinit(cell);
975 *
do_cell_integral_global(integrator, dst, src);
984 * <a name=
"step_75-Solverandpreconditioner"></a>
985 * <h3>Solver and preconditioner</h3>
990 * <a name=
"step_75-Conjugategradientsolverwithmultigridpreconditioner"></a>
991 * <h4>Conjugate-
gradient solver with multigrid preconditioner</h4>
995 * This function solves the equation system with a sequence of provided
996 * multigrid objects. It is meant to be treated as
general as possible, hence
997 * the multitude of
template parameters.
1002 *
typename SystemMatrixType,
1003 *
typename LevelMatrixType,
1004 *
typename MGTransferType>
1008 *
const VectorType &src,
1009 *
const MultigridParameters &mg_data,
1011 *
const SystemMatrixType &fine_matrix,
1012 *
const MGLevelObject<std::unique_ptr<LevelMatrixType>> &mg_matrices,
1013 *
const MGTransferType &mg_transfer)
1015 *
AssertThrow(mg_data.coarse_solver.type ==
"cg_with_amg",
1016 *
ExcNotImplemented());
1017 *
AssertThrow(mg_data.smoother.type ==
"chebyshev", ExcNotImplemented());
1019 *
const unsigned int min_level = mg_matrices.min_level();
1020 *
const unsigned int max_level = mg_matrices.max_level();
1025 *
SmootherPreconditionerType>;
1030 * We initialize
level operators and Chebyshev smoothers here.
1036 *
min_level, max_level);
1040 *
smoother_data[
level].preconditioner =
1041 *
std::make_shared<SmootherPreconditionerType>();
1042 *
mg_matrices[
level]->compute_inverse_diagonal(
1043 *
smoother_data[
level].preconditioner->get_vector());
1044 *
smoother_data[
level].smoothing_range = mg_data.smoother.smoothing_range;
1045 *
smoother_data[
level].degree = mg_data.smoother.degree;
1046 *
smoother_data[
level].eig_cg_n_iterations =
1047 *
mg_data.smoother.eig_cg_n_iterations;
1052 *
mg_smoother.
initialize(mg_matrices, smoother_data);
1056 * Next, we initialize the coarse-grid solver. We use conjugate-
gradient
1057 * method with AMG as preconditioner.
1060 *
ReductionControl coarse_grid_solver_control(mg_data.coarse_solver.maxiter,
1061 *
mg_data.coarse_solver.abstol,
1062 *
mg_data.coarse_solver.reltol,
1067 *
std::unique_ptr<MGCoarseGridBase<VectorType>> mg_coarse;
1072 *
#ifdef DEAL_II_TRILINOS_WITH_EPETRA
1073 *
amg_data.n_cycles = mg_data.coarse_solver.n_cycles;
1075 *
amg_data.smoother_type = mg_data.coarse_solver.smoother_type.c_str();
1077 *
precondition_amg.initialize(mg_matrices[min_level]->get_system_matrix(),
1084 *
decltype(precondition_amg)>>(
1085 *
coarse_grid_solver, *mg_matrices[min_level], precondition_amg);
1089 * Finally, we create the
Multigrid object, convert it to a preconditioner,
1090 * and use it
inside of a conjugate-
gradient solver to solve the linear
1091 * system of equations.
1095 *
mg_matrix, *mg_coarse, mg_transfer, mg_smoother, mg_smoother);
1097 *
PreconditionerType preconditioner(dof,
mg, mg_transfer);
1100 *
.
solve(fine_matrix, dst, src, preconditioner);
1108 * <a name=
"step_75-Hybridpolynomialgeometricglobalcoarseningmultigridpreconditioner"></a>
1109 * <h4>Hybrid polynomial/geometric-global-coarsening multigrid preconditioner</h4>
1113 * The above function deals with the actual solution
for a given sequence of
1114 * multigrid objects. This
functions creates the actual multigrid levels, in
1115 * particular the operators, and the transfer
operator as a
1119 *
template <
typename VectorType,
typename OperatorType,
int dim>
1121 *
const OperatorType &system_matrix,
1123 *
const VectorType &src,
1124 *
const MultigridParameters &mg_data,
1132 * as well as, create transfer operators. To be able to
1134 * via global coarsening of p or h. For latter, we need also a sequence
1140 * In
case no h-transfer is requested, we provide an empty deleter
for the
1142 * an external field and its destructor is called somewhere
else.
1149 *
std::vector<std::shared_ptr<const Triangulation<dim>>>
1150 *
coarse_grid_triangulations;
1151 *
if (mg_data.transfer.perform_h_transfer)
1152 *
coarse_grid_triangulations =
1154 *
dof_handler.get_triangulation());
1156 *
coarse_grid_triangulations.emplace_back(
1157 *
&(dof_handler.get_triangulation()), [](
auto *) {});
1161 * Determine the total number of levels
for the multigrid operation and
1162 * allocate sufficient memory
for all levels.
1165 *
const unsigned int n_h_levels = coarse_grid_triangulations.size() - 1;
1167 *
const auto get_max_active_fe_degree = [&](
const auto &dof_handler) {
1168 *
unsigned int max = 0;
1170 *
for (
auto &cell : dof_handler.active_cell_iterators())
1171 *
if (cell->is_locally_owned())
1173 *
std::
max(
max, dof_handler.get_fe(cell->active_fe_index()).degree);
1178 *
const unsigned int n_p_levels =
1180 *
get_max_active_fe_degree(dof_handler), mg_data.transfer.p_sequence)
1183 *
std::map<unsigned int, unsigned int> fe_index_for_degree;
1184 *
for (
unsigned int i = 0; i < dof_handler.get_fe_collection().
size(); ++i)
1186 *
const unsigned int degree = dof_handler.get_fe(i).degree;
1187 *
Assert(fe_index_for_degree.find(degree) == fe_index_for_degree.end(),
1188 *
ExcMessage(
"FECollection does not contain unique degrees."));
1189 *
fe_index_for_degree[degree] = i;
1192 *
unsigned int minlevel = 0;
1193 *
unsigned int maxlevel = n_h_levels + n_p_levels - 1;
1195 *
dof_handlers.resize(minlevel, maxlevel);
1196 *
operators.resize(minlevel, maxlevel);
1197 *
transfers.resize(minlevel, maxlevel);
1201 * Loop from the minimum (coarsest) to the maximum (finest)
level and
set up
1202 *
DoFHandler accordingly. We start with the h-levels, where we distribute
1203 * on increasingly finer meshes linear elements.
1206 *
for (
unsigned int l = 0;
l < n_h_levels; ++
l)
1208 *
dof_handlers[
l].
reinit(*coarse_grid_triangulations[l]);
1209 *
dof_handlers[
l].distribute_dofs(dof_handler.get_fe_collection());
1214 * After we reached the finest mesh, we will adjust the polynomial degrees
1215 * on each
level. We reverse iterate over our
data structure and start at
1216 * the finest mesh that contains all information about the active FE
1217 * indices. We then lower the polynomial degree of each cell
level by
level.
1220 *
for (
unsigned int i = 0, l = maxlevel; i < n_p_levels; ++i, --
l)
1222 *
dof_handlers[
l].reinit(dof_handler.get_triangulation());
1224 *
if (l == maxlevel)
1226 *
auto &dof_handler_mg = dof_handlers[
l];
1228 *
auto cell_other = dof_handler.begin_active();
1229 *
for (
auto &cell : dof_handler_mg.active_cell_iterators())
1231 *
if (cell->is_locally_owned())
1232 *
cell->set_active_fe_index(cell_other->active_fe_index());
1238 *
auto &dof_handler_fine = dof_handlers[
l + 1];
1239 *
auto &dof_handler_coarse = dof_handlers[
l + 0];
1241 *
auto cell_other = dof_handler_fine.begin_active();
1242 *
for (
auto &cell : dof_handler_coarse.active_cell_iterators())
1244 *
if (cell->is_locally_owned())
1249 *
cell_other->get_fe().degree,
1250 *
mg_data.transfer.p_sequence);
1251 *
Assert(fe_index_for_degree.find(next_degree) !=
1252 *
fe_index_for_degree.end(),
1253 *
ExcMessage(
"Next polynomial degree in sequence "
1254 *
"does not exist in FECollection."));
1256 *
cell->set_active_fe_index(fe_index_for_degree[next_degree]);
1262 *
dof_handlers[
l].distribute_dofs(dof_handler.get_fe_collection());
1267 * Next, we will create all
data structures additionally needed on each
1268 * multigrid
level. This involves determining constraints with homogeneous
1269 * Dirichlet boundary conditions, and building the
operator just like on the
1274 *
constraints(minlevel, maxlevel);
1278 *
const auto &dof_handler = dof_handlers[
level];
1279 *
auto &constraint = constraints[
level];
1281 *
constraint.reinit(dof_handler.locally_owned_dofs(),
1290 *
constraint.close();
1294 *
operators[
level] = std::make_unique<OperatorType>(mapping_collection,
1296 *
quadrature_collection,
1303 * Set up intergrid operators and collect transfer operators within a single
1304 *
operator as needed by the
Multigrid solver
class.
1309 *
dof_handlers[
level],
1310 *
constraints[
level + 1],
1311 *
constraints[
level]);
1314 *
transfers, [&](
const auto l,
auto &vec) {
1315 *
operators[
l]->initialize_dof_vector(vec);
1320 * Finally, proceed to solve the problem with multigrid.
1323 *
mg_solve(solver_control,
1338 * <a name=
"step_75-ThecodeLaplaceProblemcodeclasstemplate"></a>
1339 * <h3>The <code>LaplaceProblem</code>
class template</h3>
1343 * Now we will
finally declare the
main class of this program, which solves
1344 * the Laplace equation on subsequently refined function spaces. Its structure
1345 * will look familiar as it is similar to the
main classes of @ref step_27
"step-27" and
1346 * @ref step_40
"step-40". There are basically just two additions:
1348 * replaced by an
object of the LaplaceOperator
class for the
MatrixFree
1351 * balancing, has been added.
1354 *
template <
int dim>
1355 *
class LaplaceProblem
1358 *
LaplaceProblem(
const Parameters ¶meters);
1363 *
void initialize_grid();
1364 *
void setup_system();
1365 *
void print_diagnostics();
1366 *
void solve_system();
1367 *
void compute_indicators();
1368 *
void adapt_resolution();
1369 *
void output_results(
const unsigned int cycle);
1373 *
const Parameters prm;
1388 *
LaplaceOperator<dim, double> laplace_operator;
1392 *
std::unique_ptr<FESeries::Legendre<dim>>
legendre;
1407 * <a name=
"step_75-ThecodeLaplaceProblemcodeclassimplementation"></a>
1408 * <h3>The <code>LaplaceProblem</code>
class implementation</h3>
1413 * <a name=
"step_75-Constructor"></a>
1414 * <h4>Constructor</h4>
1418 * The constructor starts with an initializer list that looks similar to the
1420 * only the
first process to output anything over the console, and initialize
1421 * the computing timer properly.
1424 *
template <
int dim>
1425 *
LaplaceProblem<dim>::LaplaceProblem(
const Parameters ¶meters)
1426 *
: mpi_communicator(MPI_COMM_WORLD)
1428 *
, triangulation(mpi_communicator)
1429 *
, dof_handler(triangulation)
1430 *
, pcout(std::cout,
1432 *
, computing_timer(mpi_communicator,
1437 *
Assert(prm.min_h_level <= prm.max_h_level,
1439 *
"Triangulation level limits have been incorrectly set up."));
1440 *
Assert(prm.min_p_degree <= prm.max_p_degree,
1441 *
ExcMessage(
"FECollection degrees have been incorrectly set up."));
1445 * We need to prepare the
data structures
for the
hp-functionality in the
1446 * actual body of the constructor, and create corresponding objects
for
1447 * every degree in the specified range from the parameter
struct. As we are
1448 * only dealing with non-distorted rectangular cells, a linear mapping
1449 *
object is sufficient in
this context.
1453 * In the Parameters
struct, we provide ranges
for levels on which the
1454 * function space is operating with a reasonable resolution. The multigrid
1455 * algorithm
requires linear elements on the coarsest possible
level. So we
1456 * start with the lowest polynomial degree and fill the collection with
1457 * consecutively higher degrees until the user-specified maximum is
1463 *
for (
unsigned int degree = 1; degree <= prm.max_p_degree; ++degree)
1465 *
fe_collection.push_back(
FE_Q<dim>(degree));
1466 *
quadrature_collection.push_back(
QGauss<dim>(degree + 1));
1472 * As our FECollection contains more finite elements than we want to use
for
1473 * the finite element approximation of our solution, we would like to limit
1474 * the range on which active FE indices can operate on. For
this, the
1475 * FECollection
class allows to register a hierarchy that determines the
1476 * succeeding and preceding finite element in
case of of p-refinement and
1478 * consult
this hierarchy to determine future FE indices. We will
register
1479 * such a hierarchy that only works on finite elements with polynomial
1480 * degrees in the proposed range <code>[min_p_degree, max_p_degree]</code>.
1483 *
const unsigned int min_fe_index = prm.min_p_degree - 1;
1484 *
fe_collection.set_hierarchy(
1487 *
const unsigned int fe_index) ->
unsigned int {
1488 *
return ((fe_index + 1) < fe_collection.
size()) ? fe_index + 1 :
1493 *
const unsigned int fe_index) ->
unsigned int {
1494 *
Assert(fe_index >= min_fe_index,
1495 *
ExcMessage(
"Finite element is not part of hierarchy!"));
1502 *
for smoothness estimation.
1505 *
legendre = std::make_unique<FESeries::Legendre<dim>>(
1510 * The next part is going to be tricky. During execution of refinement, a
1511 * few
hp-algorithms need to interfere with the actual refinement process on
1514 * the actual refinement process and trigger all connected
functions. We
1515 * require
this functionality
for load balancing and to limit the polynomial
1516 * degrees of neighboring cells.
1520 * For the former, we would like to assign a weight to every cell that is
1521 * proportional to the number of degrees of freedom of its future finite
1523 * easily attach individual weights at the right place during the refinement
1524 * process, i.e., after all
refine and
coarsen flags have been
set correctly
1525 *
for hp-adaptation and right before repartitioning
for load balancing is
1526 * about to happen.
Functions can be registered that will attach weights in
1527 * the form that @f$a (n_\text{dofs})^b@f$ with a provided pair of parameters
1528 * @f$(a,b)@f$. We
register such a function in the following.
1532 * Since we are only
using information about the number of degrees of
1533 * freedom per cell, which is a quantity unique to every finite element, we
1534 * can compute the weights in
advance.
1538 * For
load balancing, efficient solvers like the
one we use should
scale
1539 * linearly with the number of degrees of freedom owned. We
set the
1540 * parameters
for cell weighting correspondingly:
A weighting factor of @f$1@f$
1541 * and an exponent of @f$1@f$ (see the definitions of the `weighting_factor` and
1542 * `weighting_exponent` above).
1547 *
{prm.weighting_factor, prm.weighting_exponent});
1548 *
const std::vector<unsigned int> precomputed_weights =
1550 *
weighting_function);
1551 *
cell_weights.reinit(dof_handler, precomputed_weights);
1555 * In h-adaptive applications, we ensure a 2:1 mesh balance by limiting the
1556 * difference of refinement levels of neighboring cells to
one. With the
1557 *
second call in the following code snippet, we will ensure the same
for
1558 * p-levels on neighboring cells: levels of future finite elements are not
1559 * allowed to differ by more than a specified difference. The function
1561 * be connected to a very specific signal in the
parallel context. The issue
1562 * is that we need to know how the mesh will be actually refined to
set
1563 * future FE indices accordingly. As we ask the p4est oracle to perform
1564 * refinement, we need to ensure that the
Triangulation has been updated
1565 * with the adaptation flags of the oracle
first. An instantiation of
1567 * that
for the duration of its life. Thus, we will create an
object of
this
1568 *
class right before limiting the p-
level difference, and connect the
1569 * corresponding
lambda function to the signal
1571 * after the oracle got refined, but before the
Triangulation is refined.
1572 * Furthermore, we specify that
this function will be connected to the front
1573 * of the signal, to ensure that the modification is performed before any
1574 * other function connected to the same signal.
1578 *
[&, min_fe_index]() {
1580 *
refine_modifier(triangulation);
1582 *
prm.max_p_level_difference,
1585 *
boost::signals2::at_front);
1593 * <a name=
"step_75-LaplaceProbleminitialize_grid"></a>
1594 * <h4>LaplaceProblem::initialize_grid</h4>
1599 * as demonstrated in @ref step_50
"step-50". However in the 2
d case, that particular
1600 * function removes the
first quadrant,
while we need the fourth quadrant
1601 * removed in our scenario. Thus, we will use a different function
1603 * the mesh. Furthermore, we formulate that function in a way that it also
1604 * generates a 3d mesh: the 2d L-shaped domain will basically elongated by 1
1605 * in the positive z-direction.
1610 * The parameters that we need to provide are
Point objects
for the lower left
1611 * and top right corners, as well as the number of repetitions that the base
1612 * mesh will have in each direction. We provide them
for the
first two
1613 * dimensions and treat the higher third dimension separately.
1617 * To create a L-shaped domain, we need to remove the excess cells. For this,
1618 * we specify the <code>cells_to_remove</code> accordingly. We would like to
1619 * remove one cell in every cell from the negative direction, but remove one
1620 * from the positive x-direction.
1624 * On the coarse grid, we
set the initial active FE indices and distribute the
1625 * degrees of freedom once. We do that in order to assign the
hp::FECollection
1626 * to the
DoFHandler, so that all cells know how many DoFs they are going to
1627 * have. This step is mandatory
for the weighted load balancing algorithm,
1628 * which will be called implicitly in
1632 *
template <
int dim>
1633 *
void LaplaceProblem<dim>::initialize_grid()
1637 *
std::vector<unsigned int> repetitions(dim);
1639 *
for (
unsigned int d = 0;
d < dim; ++
d)
1642 *
repetitions[
d] = 2;
1643 *
bottom_left[
d] = -1.;
1644 *
top_right[
d] = 1.;
1648 *
repetitions[
d] = 1;
1649 *
bottom_left[
d] = 0.;
1650 *
top_right[
d] = 1.;
1653 *
std::vector<int> cells_to_remove(dim, 1);
1654 *
cells_to_remove[0] = -1;
1657 *
triangulation, repetitions, bottom_left, top_right, cells_to_remove);
1659 *
const unsigned int min_fe_index = prm.min_p_degree - 1;
1660 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1661 *
if (cell->is_locally_owned())
1662 *
cell->set_active_fe_index(min_fe_index);
1664 *
dof_handler.distribute_dofs(fe_collection);
1666 *
triangulation.refine_global(prm.min_h_level);
1674 * <a name=
"step_75-LaplaceProblemsetup_system"></a>
1675 * <h4>LaplaceProblem::setup_system</h4>
1679 * This function looks exactly the same to the
one of @ref step_40
"step-40", but you will
1680 * notice the absence of the system
matrix as well as the scaffold that
1681 * surrounds it. Instead, we will initialize the
MatrixFree formulation of the
1682 * <code>laplace_operator</code> here. For boundary conditions, we will use
1683 * the Solution
class introduced earlier in this tutorial.
1686 *
template <
int dim>
1687 *
void LaplaceProblem<dim>::setup_system()
1691 *
dof_handler.distribute_dofs(fe_collection);
1693 *
locally_owned_dofs = dof_handler.locally_owned_dofs();
1694 *
locally_relevant_dofs =
1697 *
locally_relevant_solution.reinit(locally_owned_dofs,
1698 *
locally_relevant_dofs,
1699 *
mpi_communicator);
1700 *
system_rhs.reinit(locally_owned_dofs, mpi_communicator);
1702 *
constraints.clear();
1703 *
constraints.reinit(locally_owned_dofs, locally_relevant_dofs);
1706 *
mapping_collection, dof_handler, 0, Solution<dim>(), constraints);
1707 *
constraints.close();
1709 *
laplace_operator.reinit(mapping_collection,
1711 *
quadrature_collection,
1721 * <a name=
"step_75-LaplaceProblemprint_diagnostics"></a>
1722 * <h4>LaplaceProblem::print_diagnostics</h4>
1726 * This is a function that prints additional diagnostics about the equation
1727 * system and its partitioning. In addition to the usual global number of
1728 * active cells and degrees of freedom, we also output their local
1729 * equivalents. For a regulated output, we will communicate the local
1731 * which will then output all information. Output of local quantities is
1732 * limited to the
first 8 processes to avoid cluttering the terminal.
1736 * On all other processes, the containers
for the collected
data remain empty.
1737 * To ensure that we
do not access
invalid memory with the insertion
operator
1738 * (`<<`) on these processes, we need to check that the containers are not
1743 * Furthermore, we would like to print the frequencies of the polynomial
1744 * degrees in the numerical discretization. Since
this information is only
1745 * stored locally, we will count the finite elements on locally owned cells
1749 *
template <int dim>
1750 *
void LaplaceProblem<dim>::print_diagnostics()
1752 *
const unsigned int first_n_processes =
1753 *
std::min<unsigned int>(8,
1755 *
const bool output_cropped =
1759 *
pcout <<
" Number of active cells: "
1760 *
<< triangulation.n_global_active_cells() << std::endl
1761 *
<<
" by partition: ";
1763 *
const std::vector<unsigned int> n_active_cells_per_subdomain =
1765 *
triangulation.n_locally_owned_active_cells());
1766 *
for (
unsigned int i = 0; i < first_n_processes; ++i)
1767 *
if (n_active_cells_per_subdomain.size() > 0)
1768 *
pcout <<
' ' << n_active_cells_per_subdomain[i];
1769 *
if (output_cropped)
1771 *
pcout << std::endl;
1775 *
pcout <<
" Number of degrees of freedom: " << dof_handler.n_dofs()
1777 *
<<
" by partition: ";
1779 *
std::vector<types::global_dof_index> n_dofs_per_subdomain =
1781 *
dof_handler.n_locally_owned_dofs());
1782 *
for (
unsigned int i = 0; i < first_n_processes; ++i)
1783 *
if (n_dofs_per_subdomain.size() > 0)
1784 *
pcout <<
' ' << n_dofs_per_subdomain[i];
1785 *
if (output_cropped)
1787 *
pcout << std::endl;
1791 *
std::vector<types::global_dof_index> n_constraints_per_subdomain =
1794 *
pcout <<
" Number of constraints: "
1795 *
<< std::accumulate(n_constraints_per_subdomain.begin(),
1796 *
n_constraints_per_subdomain.end(),
1799 *
<<
" by partition: ";
1800 *
for (
unsigned int i = 0; i < first_n_processes; ++i)
1801 *
if (n_constraints_per_subdomain.size() > 0)
1802 *
pcout <<
' ' << n_constraints_per_subdomain[i];
1803 *
if (output_cropped)
1805 *
pcout << std::endl;
1809 *
std::vector<unsigned int> n_fe_indices(fe_collection.
size(), 0);
1810 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1811 *
if (cell->is_locally_owned())
1812 *
n_fe_indices[cell->active_fe_index()]++;
1816 *
pcout <<
" Frequencies of poly. degrees:";
1817 *
for (
unsigned int i = 0; i < fe_collection.
size(); ++i)
1818 *
if (n_fe_indices[i] > 0)
1819 *
pcout <<
' ' << fe_collection[i].degree <<
':' << n_fe_indices[i];
1820 *
pcout << std::endl;
1829 * <a name=
"step_75-LaplaceProblemsolve_system"></a>
1830 * <h4>LaplaceProblem::solve_system</h4>
1834 * The scaffold around the solution is similar to the
one of @ref step_40
"step-40". We
1835 * prepare a vector that matches the requirements of
MatrixFree and collect
1836 * the locally-relevant degrees of freedoms we solved the equation system. The
1837 * solution happens with the function introduced earlier.
1840 *
template <
int dim>
1841 *
void LaplaceProblem<dim>::solve_system()
1846 *
laplace_operator.initialize_dof_vector(completely_distributed_solution);
1849 *
prm.tolerance_factor * system_rhs.l2_norm());
1851 *
solve_with_gmg(solver_control,
1853 *
completely_distributed_solution,
1856 *
mapping_collection,
1858 *
quadrature_collection);
1860 *
pcout <<
" Solved in " << solver_control.last_step() <<
" iterations."
1863 *
constraints.distribute(completely_distributed_solution);
1865 *
locally_relevant_solution.copy_locally_owned_data_from(
1866 *
completely_distributed_solution);
1867 *
locally_relevant_solution.update_ghost_values();
1875 * <a name=
"step_75-LaplaceProblemcompute_indicators"></a>
1876 * <h4>LaplaceProblem::compute_indicators</h4>
1880 * This function contains only a part of the typical <code>refine_grid</code>
1881 * function from other tutorials and is
new in that sense. Here, we will only
1882 * calculate all indicators
for adaptation with actually refining the grid. We
1883 *
do this for the purpose of writing all indicators to the file system, so we
1884 * store them
for later.
1888 * Since we are dealing the an elliptic problem, we will make use of the
1890 * scaling factor of the underlying face integrals to be dependent on the
1891 * actual polynomial degree of the neighboring elements is favorable in
1892 *
hp-adaptive applications @cite davydov2017hp. We can
do this by specifying
1893 * the very last parameter from the additional ones you notices. The others
1894 * are actually just the defaults.
1898 * For the purpose of
hp-adaptation, we will calculate smoothness estimates
1899 * with the strategy presented in the tutorial introduction and use the
1901 * we
set the minimal polynomial degree to 2 as it seems that the smoothness
1902 * estimation algorithms have trouble with linear elements.
1905 *
template <
int dim>
1906 *
void LaplaceProblem<dim>::compute_indicators()
1910 *
estimated_error_per_cell.grow_or_shrink(triangulation.n_active_cells());
1913 *
face_quadrature_collection,
1915 *
locally_relevant_solution,
1916 *
estimated_error_per_cell,
1925 *
hp_decision_indicators.grow_or_shrink(triangulation.n_active_cells());
1928 *
locally_relevant_solution,
1929 *
hp_decision_indicators);
1937 * <a name=
"step_75-LaplaceProblemadapt_resolution"></a>
1938 * <h4>LaplaceProblem::adapt_resolution</h4>
1942 * With the previously calculated indicators, we will
finally flag all cells
1943 *
for adaptation and also execute refinement in
this function. As in previous
1944 * tutorials, we will use the
"fixed number" strategy, but now
for
1948 *
template <
int dim>
1949 *
void LaplaceProblem<dim>::adapt_resolution()
1956 * on each cell. There is
nothing new here.
1961 * elaborated in the other deal.II tutorials:
using the fixed number
1962 * strategy, we will flag 30% of all cells
for refinement and 3%
for
1963 * coarsening, as provided in the Parameters
struct.
1968 *
estimated_error_per_cell,
1969 *
prm.refine_fraction,
1970 *
prm.coarsen_fraction);
1974 * Next, we will make all adjustments
for hp-adaptation. We want to
refine
1975 * and
coarsen those cells flagged in the previous step, but need to decide
1976 *
if we would like to
do it by adjusting the grid resolution or the
1977 * polynomial degree.
1981 * The next function call sets future FE indices according to the previously
1982 * calculated smoothness indicators as p-adaptation indicators. These
1983 * indices will only be
set on those cells that have
refine or
coarsen flags
1988 * For the p-adaptation fractions, we will take an educated guess. Since we
1989 * only expect a single singularity in our scenario, i.e., in the origin of
1990 * the domain, and a smooth solution anywhere
else, we would like to
1991 * strongly prefer to use p-adaptation over h-adaptation. This reflects in
1992 * our choice of a fraction of 90%
for both p-refinement and p-coarsening.
1996 *
hp_decision_indicators,
1997 *
prm.p_refine_fraction,
1998 *
prm.p_coarsen_fraction);
2002 * After setting all indicators, we will remove those that exceed the
2003 * specified limits of the provided
level ranges in the Parameters
struct.
2004 * This limitation naturally arises
for p-adaptation as the number of
2005 * supplied finite elements is limited. In addition, we registered a custom
2006 * hierarchy
for p-adaptation in the constructor. Now, we need to
do this
2007 * manually in the h-adaptive context like in @ref step_31
"step-31".
2011 * We will iterate over all cells on the designated
min and
max levels and
2012 * remove the corresponding flags. As an alternative, we could also flag
2013 * these cells
for p-adaptation by setting future FE indices accordingly
2017 *
Assert(triangulation.n_levels() >= prm.min_h_level + 1 &&
2018 *
triangulation.n_levels() <= prm.max_h_level + 1,
2019 *
ExcInternalError());
2021 *
if (triangulation.n_levels() > prm.max_h_level)
2022 *
for (
const auto &cell :
2023 *
triangulation.active_cell_iterators_on_level(prm.max_h_level))
2024 *
cell->clear_refine_flag();
2026 *
for (
const auto &cell :
2027 *
triangulation.active_cell_iterators_on_level(prm.min_h_level))
2028 *
cell->clear_coarsen_flag();
2032 * At
this stage, we have both the future FE indices and the classic
refine
2033 * and
coarsen flags
set. The latter will be interpreted by
2035 * our previous modification ensures that the resulting
Triangulation stays
2036 * within the specified
level range.
2040 * Now, we would like to only impose one type of adaptation on cells, which
2041 * is what the next function will sort out
for us. In
short, on cells which
2042 * have both
types of indicators assigned, we will favor the p-adaptation
2043 * one and remove the h-adaptation one.
2046 *
hp::Refinement::choose_p_over_h(dof_handler);
2050 * In the
end, we are left to execute coarsening and refinement. Here, not
2051 * only the grid will be updated, but also all previous future FE indices
2052 * will become active.
2056 * Remember that we have attached functions to triangulation signals in the
2057 * constructor, will be triggered in this function call. So there is even
2058 * more happening: weighted repartitioning will be performed to ensure load
2059 * balancing, as well as we will limit the difference of p-levels between
2060 * neighboring cells.
2063 *
triangulation.execute_coarsening_and_refinement();
2071 * <a name="step_75-LaplaceProblemoutput_results"></a>
2072 * <h4>LaplaceProblem::output_results</h4>
2076 * Writing results to the file system in
parallel applications works exactly
2077 * like in @ref step_40 "step-40". In addition to the
data containers that we prepared
2078 * throughout the tutorial, we would also like to write out the polynomial
2079 * degree of each finite element on the grid as well as the subdomain each
2080 * cell belongs to. We prepare necessary containers
for this in the scope of
2084 *
template <
int dim>
2085 *
void LaplaceProblem<dim>::output_results(const
unsigned int cycle)
2091 *
if (cell->is_locally_owned())
2092 *
fe_degrees(cell->active_cell_index()) = cell->get_fe().degree;
2095 *
for (
auto &subd : subdomain)
2100 *
data_out.add_data_vector(locally_relevant_solution,
"solution");
2101 *
data_out.add_data_vector(fe_degrees,
"fe_degree");
2102 *
data_out.add_data_vector(subdomain,
"subdomain");
2103 *
data_out.add_data_vector(estimated_error_per_cell,
"error");
2104 *
data_out.add_data_vector(hp_decision_indicators,
"hp_indicator");
2105 *
data_out.build_patches(mapping_collection);
2107 *
data_out.write_vtu_with_pvtu_record(
2108 *
"./",
"solution", cycle, mpi_communicator, 2, 1);
2116 * <a name=
"step_75-LaplaceProblemrun"></a>
2117 * <h4>LaplaceProblem::run</h4>
2121 * The actual
run function again looks very familiar to @ref step_40
"step-40". The only
2122 * addition is the bracketed section that precedes the actual cycle
loop.
2123 * Here, we will pre-calculate the Legendre transformation matrices. In
2124 *
general, these will be calculated on the fly via lazy allocation whenever a
2125 * certain
matrix is needed. For timing purposes however, we would like to
2126 * calculate them all at once before the actual time measurement begins. We
2127 * will thus designate their calculation to their own scope.
2130 *
template <
int dim>
2131 *
void LaplaceProblem<dim>::run()
2133 *
pcout <<
"Running with Trilinos on "
2135 *
<<
" MPI rank(s)..." << std::endl;
2138 *
pcout <<
"Calculating transformation matrices..." << std::endl;
2140 *
legendre->precalculate_all_transformation_matrices();
2143 *
for (
unsigned int cycle = 0; cycle < prm.n_cycles; ++cycle)
2145 *
pcout <<
"Cycle " << cycle <<
':' << std::endl;
2148 *
initialize_grid();
2150 *
adapt_resolution();
2154 *
print_diagnostics();
2158 *
compute_indicators();
2161 *
output_results(cycle);
2163 *
computing_timer.print_summary();
2164 *
computing_timer.reset();
2166 *
pcout << std::endl;
2176 * <a name=
"step_75-main"></a>
2181 * The
final function is the <code>
main</code> function that will ultimately
2182 * create and
run a LaplaceOperator instantiation. Its structure is similar to
2183 * most other tutorial programs.
2186 *
int main(
int argc,
char *argv[])
2190 *
using namespace dealii;
2191 *
using namespace Step75;
2196 *
LaplaceProblem<2> laplace_problem(prm);
2197 *
laplace_problem.run();
2199 *
catch (std::exception &exc)
2201 *
std::cerr << std::endl
2203 *
<<
"----------------------------------------------------"
2205 *
std::cerr <<
"Exception on processing: " << std::endl
2206 *
<< exc.what() << std::endl
2207 *
<<
"Aborting!" << std::endl
2208 *
<<
"----------------------------------------------------"
2215 *
std::cerr << std::endl
2217 *
<<
"----------------------------------------------------"
2219 *
std::cerr <<
"Unknown exception!" << std::endl
2220 *
<<
"Aborting!" << std::endl
2221 *
<<
"----------------------------------------------------"
2229<a name=
"step_75-Results"></a><h1>Results</h1>
2232When you
run the program with the given parameters on four processes in
2233release mode, your terminal output should look like
this:
2235Running with Trilinos on 4
MPI rank(s)...
2236Calculating transformation matrices...
2238 Number of active cells: 3072
2240 Number of degrees of freedom: 12545
2242 Number of constraints: 542
2244 Frequencies of poly. degrees: 2:3072
2245 Solved in 7 iterations.
2248+---------------------------------------------+------------+------------+
2249| Total wallclock time elapsed since start | 0.172s | |
2251| Section | no. calls | wall time | % of total |
2252+---------------------------------+-----------+------------+------------+
2253| calculate transformation | 1 | 0.0194s | 11% |
2254| compute indicators | 1 | 0.00676s | 3.9% |
2255| initialize grid | 1 | 0.011s | 6.4% |
2256| output results | 1 | 0.0343s | 20% |
2257| setup system | 1 | 0.00839s | 4.9% |
2258| solve system | 1 | 0.0896s | 52% |
2259+---------------------------------+-----------+------------+------------+
2263 Number of active cells: 3351
2265 Number of degrees of freedom: 18228
2267 Number of constraints: 1202
2269 Frequencies of poly. degrees: 2:2522 3:829
2270 Solved in 7 iterations.
2273+---------------------------------------------+------------+------------+
2274| Total wallclock time elapsed since start | 0.165s | |
2276| Section | no. calls | wall time | % of total |
2277+---------------------------------+-----------+------------+------------+
2278| adapt resolution | 1 | 0.00473s | 2.9% |
2279| compute indicators | 1 | 0.00764s | 4.6% |
2280| output results | 1 | 0.0243s | 15% |
2281| setup system | 1 | 0.00662s | 4% |
2282| solve system | 1 | 0.121s | 74% |
2283+---------------------------------+-----------+------------+------------+
2290 Number of active cells: 5610
2292 Number of degrees of freedom: 82047
2294 Number of constraints: 14357
2296 Frequencies of poly. degrees: 2:1126 3:1289 4:2725 5:465 6:5
2297 Solved in 7 iterations.
2300+---------------------------------------------+------------+------------+
2301| Total wallclock time elapsed since start | 1.83s | |
2303| Section | no. calls | wall time | % of total |
2304+---------------------------------+-----------+------------+------------+
2305| adapt resolution | 1 | 0.00834s | 0.46% |
2306| compute indicators | 1 | 0.0178s | 0.97% |
2307| output results | 1 | 0.0434s | 2.4% |
2308| setup system | 1 | 0.0165s | 0.9% |
2309| solve system | 1 | 1.74s | 95% |
2310+---------------------------------+-----------+------------+------------+
2313When running the code with more processes, you will notice slight
2314differences in the number of active cells and degrees of freedom. This
2315is due to the fact that solver and preconditioner depend on the
2316partitioning of the problem, which might yield to slight differences of
2317the solution in the last digits and ultimately yields to different
2320Furthermore, the number of iterations
for the solver stays about the
2321same in all cycles despite
hp-adaptation, indicating the robustness of
2322the proposed algorithms and promising good scalability
for even larger
2323problem sizes and on more processes.
2325Let us have a look at the graphical output of the program. After all
2326refinement cycles in the given parameter configuration, the actual
2327discretized function space looks like the following with its
2328partitioning on twelve processes on the left and the polynomial degrees
2329of finite elements on the right. In the left picture, each color
2330represents a unique subdomain. In the right picture, the lightest color
2331corresponds to the polynomial degree two and the darkest
one corresponds
2334<div
class=
"twocolumn" style=
"width: 80%; text-align: center;">
2336 <img src=
"https://dealii.org/images/steps/developer/step-75.subdomains-07.svg"
2337 alt=
"Partitioning after seven refinements.">
2340 <img src=
"https://dealii.org/images/steps/developer/step-75.fedegrees-07.svg"
2341 alt=
"Local approximation degrees after seven refinements.">
2347<a name=
"step-75-extensions"></a>
2348<a name=
"step_75-Possibilitiesforextensions"></a><h3>Possibilities
for extensions</h3>
2351This tutorial shows only
one particular way how to use
parallel
2352hp-adaptive finite element methods. In the following paragraphs, you
2353will get to know which alternatives are possible. Most of these
2354extensions are already part of https:
2355which provides you with implementation examples that you can play
2359<a name=
"step_75-Differenthpdecisionstrategies"></a><h4>Different
hp-decision strategies</h4>
2362The deal.II library offers multiple strategies to decide which type of
2363adaptation to impose on cells: either adjust the grid resolution or
2364change the polynomial degree. We only presented the <i>Legendre
2365coefficient decay</i> strategy in
this tutorial,
while @ref step_27
"step-27"
2366demonstrated the <i>Fourier</i> equivalent of the same idea.
2368See the
"possibilities for extensions" section of @ref step_27
"step-27" for an
2369overview over these strategies, or the corresponding documentation
2370for a detailed description.
2372There, another strategy is mentioned that has not been shown in any
2373tutorial so far: the strategy based on <i>refinement history</i>. The
2374usage of
this method
for parallel distributed applications is more
2375tricky than the others, so we will highlight the challenges that come
2376along with it. We need information about the
final state of refinement
2377flags, and we need to transfer the solution across refined meshes. For
2379function to the
Triangulation::Signals::post_p4est_refinement signal in
2380a way that it will be called <i>after</i> the
2381hp::Refinement::limit_p_level_difference() function. At this stage, all
2382refinement flags and future FE indices are terminally
set and a reliable
2383prediction of the error is possible. The predicted error then needs to
2384be transferred across refined meshes with the aid of
2387Try implementing one of these strategies into this tutorial and observe
2388the subtle changes to the results. You will notice that all strategies
2389are capable of identifying the singularities near the reentrant corners
2390and will perform @f$h@f$-refinement in these regions, while preferring
2391@f$p@f$-refinement in the bulk domain. A detailed comparison of these
2392strategies is presented in @cite fehling2020 .
2395<a name="step_75-Solvewithmatrixbasedmethods"></a><h4>Solve with matrix-based methods</h4>
2398This tutorial focuses solely on matrix-free strategies. All
hp-adaptive
2399algorithms however also work with matrix-based approaches in the
2402To create a system matrix, you can either use the
2403LaplaceOperator::get_system_matrix() function, or use an
2404<code>assemble_system()</code> function similar to the one of @ref step_27 "step-27".
2405You can then pass the system matrix to the solver as usual.
2407You can time the results of both matrix-based and matrix-free
2408implementations, quantify the speed-up, and convince yourself which
2412<a name="step_75-Multigridvariants"></a><h4>
Multigrid variants</h4>
2415For sake of simplicity, we have restricted ourselves to a single type of
2416coarse-grid solver (CG with AMG), smoother (Chebyshev smoother with
2417point Jacobi preconditioner), and geometric-coarsening scheme (global
2418coarsening) within the multigrid algorithm. Feel free to try out
2419alternatives and investigate their performance and robustness.
2422<a name="step_75-PlainProg"></a>
2423<h1> The plain program</h1>
2424@include "step-75.cc"
* * triangulation refine_global(n_initial_global_refinements)
* * for(const auto &cell :triangulation.active_cell_iterators())
* * int main(int argc, char **argv)
* x_component_mask set(0, true)
* * * struct InterferenceTaperTransform *
void attach_dof_handler(const DoFHandler< dim, spacedim > &)
void reinit(const Triangulation< dim, spacedim > &tria)
void evaluate(const EvaluationFlags::EvaluationFlags evaluation_flag)
static void estimate(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Quadrature< dim - 1 > &quadrature, const std::map< types::boundary_id, const Function< spacedim, Number > * > &neumann_bc, const ReadVector< Number > &solution, Vector< float > &error, const ComponentMask &component_mask={}, 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)
void initialize(const MGLevelObject< MatrixType2 > &matrices, const typename PreconditionerType::AdditionalData &additional_data=typename PreconditionerType::AdditionalData())
void initialize_dof_vector(VectorType &vec, const unsigned int dof_handler_index=0) const
void reinit(const MappingType &mapping, const DoFHandler< dim > &dof_handler, const AffineConstraints< number2 > &constraint, const QuadratureType &quad, const AdditionalData &additional_data=AdditionalData())
void solve(const MatrixType &A, VectorType &x, const VectorType &b, const PreconditionerType &preconditioner)
virtual types::subdomain_id locally_owned_subdomain() const
void coarsen_global(const unsigned int times=1)
virtual void execute_coarsening_and_refinement()
unsigned int size() const
std::function< unsigned int(const typename DoFHandler< dim, spacedim >::cell_iterator &, const FiniteElement< dim, spacedim > &)> WeightingFunction
static WeightingFunction ndofs_weighting(const std::pair< float, float > &coefficients)
static std::vector< unsigned int > precompute_weights(const hp::FECollection< dim, spacedim > &fe_collection, const WeightingFunction &weighting_function)
#define DEAL_II_NOT_IMPLEMENTED()
IteratorRange< active_cell_iterator > active_cell_iterators() const
#define Assert(cond, exc)
#define AssertThrow(cond, exc)
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_hanging_node_constraints(const DoFHandler< dim, spacedim > &dof_handler, AffineConstraints< number > &constraints)
void make_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern, const AffineConstraints< number > &constraints={}, const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
@ update_gradients
Shape function gradients.
std::vector< index_type > data
std::array< double, dim > to_spherical(const Point< dim > &point)
void hyper_L(Triangulation< dim > &tria, const double left=-1., const double right=1., const bool colorize=false)
void subdivided_hyper_L(Triangulation< dim, spacedim > &tria, const std::vector< unsigned int > &repetitions, const Point< dim > &bottom_left, const Point< dim > &top_right, const std::vector< int > &n_cells_to_remove)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
void coarsen(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold)
@ matrix
Contents is actually a matrix.
@ symmetric
Matrix is symmetric.
@ diagonal
Matrix is diagonal.
@ general
No special properties.
constexpr types::blas_int one
Tpetra::Vector< Number, LO, GO, NodeType< MemorySpace > > VectorType
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
Tensor< 2, dim, Number > l(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
SymmetricTensor< 2, dim, Number > b(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
* * if(update_pressure &update_flags) * compute_pressure(constitutive_request
* * * RotationFunction< dim, Number >::RotationFunction Number(dim)
* * * * std::vector< Number > ThermoPlasticMaterial< dim, ViscoplasticYieldLaw, Number >::get_state_parameters const
FESeries::Legendre< dim, spacedim > default_fe_series(const hp::FECollection< dim, spacedim > &fe_collection, const unsigned int component=numbers::invalid_unsigned_int)
void coefficient_decay(FESeries::Legendre< dim, spacedim > &fe_legendre, const DoFHandler< dim, spacedim > &dof_handler, const VectorType &solution, Vector< float > &smoothness_indicators, const VectorTools::NormType regression_strategy=VectorTools::Linfty_norm, const double smallest_abs_coefficient=1e-10, const bool only_flagged_cells=false)
T sum(const T &t, const MPI_Comm mpi_communicator)
unsigned int n_mpi_processes(const MPI_Comm mpi_communicator)
T max(const T &t, const MPI_Comm mpi_communicator)
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
std::vector< T > gather(const MPI_Comm comm, const T &object_to_send, const unsigned int root_process=0)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
void load(Archive &ar, ::std_cxx26::inplace_vector< T, N > &vec, const unsigned int)
bool limit_p_level_difference(const DoFHandler< dim, spacedim > &dof_handler, const unsigned int max_difference=1, const unsigned int contains_fe_index=0)
void predict_error(const DoFHandler< dim, spacedim > &dof_handler, const Vector< Number > &error_indicators, Vector< Number > &predicted_errors, const double gamma_p=std::sqrt(0.4), const double gamma_h=2., const double gamma_n=1.)
void p_adaptivity_fixed_number(const DoFHandler< dim, spacedim > &dof_handler, const Vector< Number > &criteria, const double p_refine_fraction=0.5, const double p_coarsen_fraction=0.5, const ComparisonFunction< std_cxx20::type_identity_t< Number > > &compare_refine=std::greater_equal< Number >(), const ComparisonFunction< std_cxx20::type_identity_t< Number > > &compare_coarsen=std::less_equal< Number >())
int(&) functions(const void *v1, const void *v2)
void reinit(MatrixBlock< MatrixType > &v, const BlockSparsityPattern &p)
constexpr unsigned int invalid_unsigned_int
constexpr types::material_id invalid_material_id
constexpr types::subdomain_id invalid_subdomain_id
void refine_and_coarsen_fixed_number(::Triangulation< dim, spacedim > &tria, const ::Vector< Number > &criteria, const double top_fraction_of_cells, const double bottom_fraction_of_cells, const types::global_cell_index max_n_cells=std::numeric_limits< types::global_cell_index >::max())
double legendre(unsigned int l, double x)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > pow(const ::VectorizedArray< Number, width > &, const Number p)
::VectorizedArray< Number, width > abs(const ::VectorizedArray< Number, width > &)
unsigned short int fe_index
UpdateFlags mapping_update_flags
boost::signals2::signal< void()> post_p4est_refinement
unsigned int smoother_sweeps
void advance(std::tuple< I1, I2 > &t, const unsigned int n)