528 *
"Diffusion parameter");
533 *
"Finite Element degree");
537 *
"Select smoother: SOR|Jacobi|block SOR|block Jacobi");
541 *
"Number of smoothing steps");
546 *
"Select DoF renumbering: none|downstream|upstream|random");
550 *
"Enable streamline diffusion stabilization: true|false");
554 *
"Generate graphical output: true|false");
557 *
if (prm_filename.empty())
561 *
false,
ExcMessage(
"Please pass a .prm file as the first argument!"));
568 * smoother_type = prm.
get(
"Smoother type");
569 * smoothing_steps = prm.
get_integer(
"Smoothing steps");
571 *
const std::string renumbering = prm.
get(
"DoF renumbering");
572 *
if (renumbering ==
"none")
574 *
else if (renumbering ==
"downstream")
576 *
else if (renumbering ==
"upstream")
577 * dof_renumbering = DoFRenumberingStrategy::upstream;
578 *
else if (renumbering ==
"random")
582 *
ExcMessage(
"The <DoF renumbering> parameter has " 583 *
"an invalid value."));
585 * with_streamline_diffusion = prm.
get_bool(
"With streamline diffusion");
593 * <a name=
"Cellpermutations"></a>
594 * <h3>Cell permutations</h3>
598 * The ordering in which cells and degrees of freedom are traversed
599 * will play a role in the speed of convergence
for multiplicative
600 * methods. Here we define
functions which
return a specific ordering
601 * of cells to be used by the block smoothers.
605 * For each type of cell ordering, we define a
function for the
606 * active mesh and
one for a
level mesh (i.e.,
for the cells at
one 607 *
level of a multigrid hierarchy). While the only reordering
608 * necessary
for solving the system will be on the
level meshes, we
609 * include the active reordering
for visualization purposes in
615 * array with all of the relevant cells that we then sort in
616 *
downstream direction
using a
"comparator" object. The output of
617 * the functions is then simply an array of the indices of the cells
618 * in the just computed order.
622 * std::vector<unsigned int>
625 *
const unsigned int level)
627 * std::vector<typename DoFHandler<dim>::level_cell_iterator> ordered_cells;
628 * ordered_cells.reserve(dof_handler.get_triangulation().n_cells(
level));
629 *
for (
const auto &cell : dof_handler.cell_iterators_on_level(
level))
630 * ordered_cells.push_back(cell);
633 * CompareDownstream<typename DoFHandler<dim>::level_cell_iterator, dim>
634 * comparator(direction);
635 * std::sort(ordered_cells.begin(), ordered_cells.end(), comparator);
637 * std::vector<unsigned> ordered_indices;
638 * ordered_indices.reserve(dof_handler.get_triangulation().n_cells(
level));
640 *
for (
const auto &cell : ordered_cells)
641 * ordered_indices.push_back(cell->index());
643 *
return ordered_indices;
649 * std::vector<unsigned int>
653 * std::vector<typename DoFHandler<dim>::active_cell_iterator> ordered_cells;
654 * ordered_cells.reserve(dof_handler.get_triangulation().n_active_cells());
655 *
for (
const auto &cell : dof_handler.active_cell_iterators())
656 * ordered_cells.push_back(cell);
659 * CompareDownstream<typename DoFHandler<dim>::active_cell_iterator, dim>
660 * comparator(direction);
661 * std::sort(ordered_cells.begin(), ordered_cells.end(), comparator);
663 * std::vector<unsigned int> ordered_indices;
664 * ordered_indices.reserve(dof_handler.get_triangulation().n_active_cells());
666 *
for (
const auto &cell : ordered_cells)
667 * ordered_indices.push_back(cell->index());
669 *
return ordered_indices;
675 * The functions that produce a
random ordering are similar in
676 * spirit in that they
first put information about all cells into an
677 * array. But then, instead of sorting them, they shuffle the
678 * elements randomly
using the facilities
C++ offers to generate
679 *
random numbers. The way
this is done is by iterating over all
680 * elements of the array, drawing a
random number
for another
681 * element before that, and then exchanging these elements. The
682 * result is a
random shuffle of the elements of the array.
686 * std::vector<unsigned int>
688 *
const unsigned int level)
690 * std::vector<unsigned int> ordered_cells;
691 * ordered_cells.reserve(dof_handler.get_triangulation().n_cells(
level));
692 *
for (
const auto &cell : dof_handler.cell_iterators_on_level(
level))
693 * ordered_cells.push_back(cell->index());
695 * std::mt19937 random_number_generator;
696 * std::shuffle(ordered_cells.begin(),
697 * ordered_cells.end(),
698 * random_number_generator);
700 *
return ordered_cells;
706 * std::vector<unsigned int>
709 * std::vector<unsigned int> ordered_cells;
710 * ordered_cells.reserve(dof_handler.get_triangulation().n_active_cells());
711 *
for (
const auto &cell : dof_handler.active_cell_iterators())
712 * ordered_cells.push_back(cell->index());
714 * std::mt19937 random_number_generator;
715 * std::shuffle(ordered_cells.begin(),
716 * ordered_cells.end(),
717 * random_number_generator);
719 *
return ordered_cells;
726 * <a name=
"Righthandsideandboundaryvalues"></a>
727 * <h3>Right-hand side and boundary values</h3>
731 * The problem solved in
this tutorial is an adaptation of Ex. 3.1.3 found
733 * href=
"https://global.oup.com/academic/product/finite-elements-and-fast-iterative-solvers-9780199678808">
734 * Finite Elements and Fast Iterative Solvers: with Applications in
735 * Incompressible Fluid Dynamics by Elman, Silvester, and Wathen</a>. The
736 * main difference being that we add a hole in the
center of our domain with
737 *
zero Dirichlet boundary conditions.
741 * For a complete description, we need classes that implement the
742 *
zero right-hand side
first (we could of course have just used
747 * class RightHandSide : public
Function<dim>
751 *
const unsigned int component = 0)
const override;
753 *
virtual void value_list(
const std::vector<
Point<dim>> &points,
754 * std::vector<double> & values,
755 *
const unsigned int component = 0)
const override;
762 *
const unsigned int component)
const 773 *
void RightHandSide<dim>::value_list(
const std::vector<
Point<dim>> &points,
774 * std::vector<double> & values,
775 *
const unsigned int component)
const 777 *
Assert(values.size() == points.size(),
780 *
for (
unsigned int i = 0; i < points.size(); ++i)
787 * We also have Dirichlet boundary conditions. On a connected portion of the
788 * outer, square boundary we
set the
value to 1, and we
set the
value to 0
789 * everywhere
else (including the inner, circular boundary):
793 *
class BoundaryValues :
public Function<dim>
797 *
const unsigned int component = 0)
const override;
800 * std::vector<double> & values,
801 *
const unsigned int component = 0)
const override;
808 *
const unsigned int component)
const 815 * Set boundary to 1
if @f$x=1@f$, or
if @f$x>0.5@f$ and @f$y=-1@f$.
819 * (
std::fabs(p[1] + 1) < 1
e-8 && p[0] >= 0.5))
832 *
void BoundaryValues<dim>::value_list(
const std::vector<
Point<dim>> &points,
833 * std::vector<double> & values,
834 *
const unsigned int component)
const 836 *
Assert(values.size() == points.size(),
839 *
for (
unsigned int i = 0; i < points.size(); ++i)
848 * <a name=
"Streamlinediffusionimplementation"></a>
849 * <h3>Streamline diffusion implementation</h3>
853 * The streamline diffusion method has a stabilization constant that
854 * we need to be able to compute. The choice of how
this parameter
855 * is computed is taken from <a
856 * href=
"https://link.springer.com/chapter/10.1007/978-3-540-34288-5_27">On
857 * Discontinuity-Capturing Methods
for Convection-Diffusion
858 * Equations by Volker John and Petr Knobloch</a>.
862 *
double compute_stabilization_delta(
const double hk,
867 *
const double Peclet = dir.norm() * hk / (2.0 *
eps * pk);
868 *
const double coth =
869 * (1.0 +
std::exp(-2.0 * Peclet)) / (1.0 - std::exp(-2.0 * Peclet));
871 *
return hk / (2.0 * dir.norm() * pk) * (coth - 1.0 / Peclet);
878 * <a name=
"codeAdvectionProlemcodeclass"></a>
879 * <h3><code>AdvectionProlem</code>
class</h3>
883 * This is the main
class of the program, and should look very similar to
884 * @ref step_16
"step-16". The major difference is that, since we are defining our multigrid
885 * smoother at runtime, we choose to define a
function `create_smoother()` and
886 * a
class object `mg_smoother` which is a `
std::unique_ptr` to a smoother
887 * that is derived from
MGSmoother. Note that for smoothers derived from
889 * This will contain information about the cell ordering and the method of
890 * inverting cell matrices.
897 * class AdvectionProblem
900 * AdvectionProblem(
const Settings &settings);
904 *
void setup_system();
906 *
template <
class IteratorType>
907 *
void assemble_cell(
const IteratorType &cell,
908 * ScratchData<dim> & scratch_data,
909 * CopyData & copy_data);
910 *
void assemble_system_and_multigrid();
912 *
void setup_smoother();
915 *
void refine_grid();
916 *
void output_results(
const unsigned int cycle)
const;
943 * std::unique_ptr<MGSmoother<Vector<double>>> mg_smoother;
945 *
using SmootherType =
947 *
using SmootherAdditionalDataType = SmootherType::AdditionalData;
960 * AdvectionProblem<dim>::AdvectionProblem(
const Settings &settings)
963 * , fe(settings.fe_degree)
964 * , mapping(settings.fe_degree)
965 * , settings(settings)
978 * <a name=
"codeAdvectionProblemsetup_systemcode"></a>
979 * <h4><code>AdvectionProblem::setup_system()</code></h4>
988 * We could renumber the active DoFs with the
DoFRenumbering class,
989 * but the smoothers only act on multigrid levels and as such,
this 990 * would not matter
for the computations. Instead, we will renumber the
991 * DoFs on each multigrid
level below.
995 *
void AdvectionProblem<dim>::setup_system()
999 * dof_handler.distribute_dofs(fe);
1001 * solution.reinit(dof_handler.n_dofs());
1002 * system_rhs.reinit(dof_handler.n_dofs());
1004 * constraints.clear();
1008 * mapping, dof_handler, 0, BoundaryValues<dim>(), constraints);
1010 * mapping, dof_handler, 1, BoundaryValues<dim>(), constraints);
1011 * constraints.close();
1019 * sparsity_pattern.copy_from(dsp);
1020 * system_matrix.reinit(sparsity_pattern);
1022 * dof_handler.distribute_mg_dofs();
1026 * Having enumerated the global degrees of freedom as well as (in
1027 * the last line above) the
level degrees of freedom, let us
1028 * renumber the
level degrees of freedom to
get a better smoother
1029 * as explained in the introduction. The
first block below
1031 * direction
if needed. This is only necessary
for point smoothers
1032 * (SOR and Jacobi) as the block smoothers operate on cells (see
1033 * `create_smoother()`). The blocks below then also implement
1037 * if (settings.smoother_type ==
"SOR" || settings.smoother_type ==
"Jacobi")
1039 *
if (settings.dof_renumbering ==
1041 * settings.dof_renumbering ==
1042 * Settings::DoFRenumberingStrategy::upstream)
1045 * (settings.dof_renumbering ==
1046 * Settings::DoFRenumberingStrategy::upstream ?
1049 * advection_direction;
1057 *
else if (settings.dof_renumbering ==
1069 * The rest of the
function just sets up data structures. The last
1070 * lines of the code below is unlike the other GMG tutorials, as
1071 * it sets up both the
interface in and out matrices. We need this
1075 * mg_constrained_dofs.clear();
1076 * mg_constrained_dofs.initialize(dof_handler);
1078 * mg_constrained_dofs.make_zero_boundary_constraints(dof_handler, {0, 1});
1080 * mg_matrices.resize(0, n_levels - 1);
1081 * mg_matrices.clear_elements();
1082 * mg_interface_in.resize(0, n_levels - 1);
1083 * mg_interface_in.clear_elements();
1084 * mg_interface_out.resize(0, n_levels - 1);
1085 * mg_interface_out.clear_elements();
1086 * mg_sparsity_patterns.resize(0, n_levels - 1);
1087 * mg_interface_sparsity_patterns.resize(0, n_levels - 1);
1093 * dof_handler.n_dofs(
level));
1095 * mg_sparsity_patterns[
level].copy_from(dsp);
1096 * mg_matrices[
level].reinit(mg_sparsity_patterns[
level]);
1100 * dof_handler.n_dofs(
level));
1102 * mg_constrained_dofs,
1105 * mg_interface_sparsity_patterns[
level].copy_from(dsp);
1107 * mg_interface_in[
level].reinit(mg_interface_sparsity_patterns[
level]);
1108 * mg_interface_out[
level].reinit(mg_interface_sparsity_patterns[level]);
1117 * <a name=
"codeAdvectionProblemassemble_cellcode"></a>
1118 * <h4><code>AdvectionProblem::assemble_cell()</code></h4>
1122 * Here we define the assembly of the linear system on each cell to
1123 * be used by the
mesh_loop() function below. This
one function
1124 * assembles the cell
matrix for either an active or a
level cell
1125 * (whatever it is passed as its
first argument), and only assembles
1126 * a right-hand side if called with an active cell.
1132 * template <
int dim>
1133 * template <class IteratorType>
1134 *
void AdvectionProblem<dim>::assemble_cell(const IteratorType &cell,
1135 * ScratchData<dim> & scratch_data,
1136 * CopyData & copy_data)
1138 * copy_data.level = cell->level();
1140 *
const unsigned int dofs_per_cell =
1141 * scratch_data.fe_values.get_fe().dofs_per_cell;
1142 * copy_data.dofs_per_cell = dofs_per_cell;
1143 * copy_data.cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
1145 *
const unsigned int n_q_points =
1146 * scratch_data.fe_values.get_quadrature().size();
1148 *
if (cell->is_level_cell() ==
false)
1149 * copy_data.cell_rhs.reinit(dofs_per_cell);
1151 * copy_data.local_dof_indices.resize(dofs_per_cell);
1152 * cell->get_active_or_mg_dof_indices(copy_data.local_dof_indices);
1154 * scratch_data.fe_values.reinit(cell);
1156 * RightHandSide<dim> right_hand_side;
1157 * std::vector<double> rhs_values(n_q_points);
1159 * right_hand_side.value_list(scratch_data.fe_values.get_quadrature_points(),
1164 * If we are
using streamline diffusion we must add its contribution
1165 * to both the cell
matrix and the cell right-hand side. If we are not
1166 *
using streamline diffusion, setting @f$\delta=0@f$ negates
this contribution
1167 * below and we are left with the standard, Galerkin finite element
1171 *
const double delta = (settings.with_streamline_diffusion ?
1172 * compute_stabilization_delta(cell->diameter(),
1174 * advection_direction,
1175 * settings.fe_degree) :
1178 *
for (
unsigned int q_point = 0; q_point < n_q_points; ++q_point)
1179 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1181 *
for (
unsigned int j = 0; j < dofs_per_cell; ++j)
1185 * The assembly of the local
matrix has two parts. First
1186 * the Galerkin contribution:
1189 * copy_data.cell_matrix(i, j) +=
1190 * (settings.epsilon *
1191 * scratch_data.fe_values.shape_grad(i, q_point) *
1192 * scratch_data.fe_values.shape_grad(j, q_point) *
1193 * scratch_data.fe_values.JxW(q_point)) +
1194 * (scratch_data.fe_values.shape_value(i, q_point) *
1195 * (advection_direction *
1196 * scratch_data.fe_values.shape_grad(j, q_point)) *
1197 * scratch_data.fe_values.JxW(q_point))
1200 * and then the streamline diffusion contribution:
1204 * (advection_direction *
1205 * scratch_data.fe_values.shape_grad(j, q_point)) *
1206 * (advection_direction *
1207 * scratch_data.fe_values.shape_grad(i, q_point)) *
1208 * scratch_data.fe_values.JxW(q_point) -
1209 * delta * settings.epsilon *
1210 *
trace(scratch_data.fe_values.shape_hessian(j, q_point)) *
1211 * (advection_direction *
1212 * scratch_data.fe_values.shape_grad(i, q_point)) *
1213 * scratch_data.fe_values.JxW(q_point);
1215 *
if (cell->is_level_cell() ==
false)
1219 * The same applies to the right hand side. First the
1220 * Galerkin contribution:
1223 * copy_data.cell_rhs(i) +=
1224 * scratch_data.fe_values.shape_value(i, q_point) *
1225 * rhs_values[q_point] * scratch_data.fe_values.JxW(q_point)
1228 * and then the streamline diffusion contribution:
1231 * + delta * rhs_values[q_point] * advection_direction *
1232 * scratch_data.fe_values.shape_grad(i, q_point) *
1233 * scratch_data.fe_values.JxW(q_point);
1242 * <a name=
"codeAdvectionProblemassemble_system_and_multigridcode"></a>
1243 * <h4><code>AdvectionProblem::assemble_system_and_multigrid()</code></h4>
1248 * system_matrix, system_rhs, and all mg_matrices for us.
1254 * template <
int dim>
1255 *
void AdvectionProblem<dim>::assemble_system_and_multigrid()
1257 *
const auto cell_worker_active =
1258 * [&](
const decltype(dof_handler.begin_active()) &cell,
1259 * ScratchData<dim> & scratch_data,
1260 * CopyData & copy_data) {
1261 * this->assemble_cell(cell, scratch_data, copy_data);
1264 *
const auto copier_active = [&](
const CopyData ©_data) {
1265 * constraints.distribute_local_to_global(copy_data.cell_matrix,
1266 * copy_data.cell_rhs,
1267 * copy_data.local_dof_indices,
1274 * dof_handler.end(),
1275 * cell_worker_active,
1277 * ScratchData<dim>(fe, fe.degree + 1),
1283 * Unlike the constraints
for the active
level, we choose to create
1284 * constraint objects
for each multigrid level local to
this function 1285 * since they are never needed elsewhere in the program.
1288 * std::vector<AffineConstraints<double>> boundary_constraints(
1290 *
for (
unsigned int level = 0; level <
triangulation.n_global_levels();
1293 *
IndexSet locally_owned_level_dof_indices;
1295 * dof_handler, level, locally_owned_level_dof_indices);
1296 * boundary_constraints[
level].reinit(locally_owned_level_dof_indices);
1297 * boundary_constraints[
level].add_lines(
1298 * mg_constrained_dofs.get_refinement_edge_indices(level));
1299 * boundary_constraints[
level].add_lines(
1300 * mg_constrained_dofs.get_boundary_indices(level));
1301 * boundary_constraints[
level].close();
1304 *
const auto cell_worker_mg =
1305 * [&](
const decltype(dof_handler.begin_mg()) &cell,
1306 * ScratchData<dim> & scratch_data,
1307 * CopyData & copy_data) {
1308 * this->assemble_cell(cell, scratch_data, copy_data);
1311 *
const auto copier_mg = [&](
const CopyData ©_data) {
1312 * boundary_constraints[copy_data.level].distribute_local_to_global(
1313 * copy_data.cell_matrix,
1314 * copy_data.local_dof_indices,
1315 * mg_matrices[copy_data.level]);
1319 * If @f$(i,j)@f$ is an `interface_out` dof pair, then @f$(j,i)@f$ is an
1320 * `interface_in` dof pair. Note: For `interface_in`, we load
1321 * the
transpose of the
interface entries, i.
e., the entry for
1322 * dof pair @f$(j,i)@f$ is stored in `interface_in(i,j)`. This is an
1323 * optimization
for the
symmetric case which allows only
one 1324 *
matrix to be used when setting the edge_matrices in
1325 * solve(). Here, however, since our problem is non-
symmetric,
1326 * we must store both `interface_in` and `interface_out`
1330 *
for (
unsigned int i = 0; i < copy_data.dofs_per_cell; ++i)
1331 *
for (
unsigned int j = 0; j < copy_data.dofs_per_cell; ++j)
1332 *
if (mg_constrained_dofs.is_interface_matrix_entry(
1334 * copy_data.local_dof_indices[i],
1335 * copy_data.local_dof_indices[j]))
1337 * mg_interface_out[copy_data.level].add(
1338 * copy_data.local_dof_indices[i],
1339 * copy_data.local_dof_indices[j],
1340 * copy_data.cell_matrix(i, j));
1341 * mg_interface_in[copy_data.level].add(
1342 * copy_data.local_dof_indices[i],
1343 * copy_data.local_dof_indices[j],
1344 * copy_data.cell_matrix(j, i));
1349 * dof_handler.end_mg(),
1352 * ScratchData<dim>(fe, fe.degree + 1),
1361 * <a name=
"codeAdvectionProblemsetup_smoothercode"></a>
1362 * <h4><code>AdvectionProblem::setup_smoother()</code></h4>
1366 * Next, we
set up the smoother based on the settings in the `.prm` file. The
1367 * two options that are of significance is the number of pre- and
1368 * post-smoothing steps on each level of the multigrid v-cycle and the
1369 * relaxation parameter.
1373 * Since multiplicative methods tend to be more powerful than additive method,
1374 * fewer smoothing steps are required to see convergence independent of mesh
1375 * size. The same holds
for block smoothers over
point smoothers. This is
1376 * reflected in the choice
for the number of smoothing steps
for each type of
1381 * The relaxation parameter
for point smoothers is chosen based on trial and
1382 * error, and reflects values necessary to keep the iteration counts in
1383 * the GMRES solve constant (or as close as possible) as we
refine the mesh.
1384 * The two values given
for both
"Jacobi" and
"SOR" in the `.prm` files are
1385 *
for degree 1 and degree 3 finite elements. If the user wants to change to
1386 * another degree, they may need to adjust these
numbers. For block smoothers,
1387 *
this parameter has a more straightforward interpretation, namely that
for 1388 * additive methods in 2D, a DoF can have a repeated contribution from up to 4
1389 * cells, therefore we must relax these methods by 0.25 to compensate. This is
1390 * not an issue
for multiplicative methods as each cell
's inverse application 1391 * carries new information to all its DoFs. 1395 * Finally, as mentioned above, the point smoothers only operate on DoFs, and 1396 * the block smoothers on cells, so only the block smoothers need to be given 1397 * information regarding cell orderings. DoF ordering for point smoothers has 1398 * already been taken care of in `setup_system()`. 1404 * template <int dim> 1405 * void AdvectionProblem<dim>::setup_smoother() 1407 * if (settings.smoother_type == "SOR") 1409 * using Smoother = PreconditionSOR<SparseMatrix<double>>; 1412 * std_cxx14::make_unique<MGSmootherPrecondition<SparseMatrix<double>, 1414 * Vector<double>>>(); 1415 * smoother->initialize(mg_matrices, 1416 * Smoother::AdditionalData(fe.degree == 1 ? 1.0 : 1418 * smoother->set_steps(settings.smoothing_steps); 1419 * mg_smoother = std::move(smoother); 1421 * else if (settings.smoother_type == "Jacobi") 1423 * using Smoother = PreconditionJacobi<SparseMatrix<double>>; 1425 * std_cxx14::make_unique<MGSmootherPrecondition<SparseMatrix<double>, 1427 * Vector<double>>>(); 1428 * smoother->initialize(mg_matrices, 1429 * Smoother::AdditionalData(fe.degree == 1 ? 0.6667 : 1431 * smoother->set_steps(settings.smoothing_steps); 1432 * mg_smoother = std::move(smoother); 1434 * else if (settings.smoother_type == "block SOR" || 1435 * settings.smoother_type == "block Jacobi") 1437 * smoother_data.resize(0, triangulation.n_levels() - 1); 1439 * for (unsigned int level = 0; level < triangulation.n_levels(); ++level) 1441 * DoFTools::make_cell_patches(smoother_data[level].block_list, 1445 * smoother_data[level].relaxation = 1446 * (settings.smoother_type == "block SOR" ? 1.0 : 0.25); 1447 * smoother_data[level].inversion = PreconditionBlockBase<double>::svd; 1449 * std::vector<unsigned int> ordered_indices; 1450 * switch (settings.dof_renumbering) 1452 * case Settings::DoFRenumberingStrategy::downstream: 1454 * create_downstream_cell_ordering(dof_handler, 1455 * advection_direction, 1459 * case Settings::DoFRenumberingStrategy::upstream: 1461 * create_downstream_cell_ordering(dof_handler, 1462 * -1.0 * advection_direction, 1466 * case Settings::DoFRenumberingStrategy::random: 1468 * create_random_cell_ordering(dof_handler, level); 1471 * case Settings::DoFRenumberingStrategy::none: 1475 * AssertThrow(false, ExcNotImplemented()); 1479 * smoother_data[level].order = 1480 * std::vector<std::vector<unsigned int>>(1, ordered_indices); 1483 * if (settings.smoother_type == "block SOR") 1485 * auto smoother = std_cxx14::make_unique<MGSmootherPrecondition< 1486 * SparseMatrix<double>, 1487 * RelaxationBlockSOR<SparseMatrix<double>, double, Vector<double>>, 1488 * Vector<double>>>(); 1489 * smoother->initialize(mg_matrices, smoother_data); 1490 * smoother->set_steps(settings.smoothing_steps); 1491 * mg_smoother = std::move(smoother); 1493 * else if (settings.smoother_type == "block Jacobi") 1495 * auto smoother = std_cxx14::make_unique< 1496 * MGSmootherPrecondition<SparseMatrix<double>, 1497 * RelaxationBlockJacobi<SparseMatrix<double>, 1500 * Vector<double>>>(); 1501 * smoother->initialize(mg_matrices, smoother_data); 1502 * smoother->set_steps(settings.smoothing_steps); 1503 * mg_smoother = std::move(smoother); 1507 * AssertThrow(false, ExcNotImplemented()); 1514 * <a name="codeAdvectionProblemsolvecode"></a> 1515 * <h4><code>AdvectionProblem::solve()</code></h4> 1519 * Before we can solve the system, we must first set up the multigrid 1520 * preconditioner. This requires the setup of the transfer between levels, 1521 * the coarse matrix solver, and the smoother. This setup follows almost 1522 * identically to @ref step_16 "step-16", the main difference being the various smoothers 1523 * defined above and the fact that we need different interface edge matrices 1524 * for in and out since our problem is non-symmetric. (In reality, for this 1525 * tutorial these interface matrices are empty since we are only using global 1526 * refinement, and thus have no refinement edges. However, we have still 1527 * included both here since if one made the simple switch to an adaptively 1528 * refined method, the program would still run correctly.) 1532 * The last thing to note is that since our problem is non-symmetric, we must 1533 * use an appropriate Krylov subspace method. We choose here to 1534 * use GMRES since it offers the guarantee of residual reduction in each 1535 * iteration. The major disavantage of GMRES is that, for each iteration, 1536 * the number of stored temporary vectors increases by one, and one also needs 1537 * to compute a scalar product with all previously stored vectors. This is 1538 * rather expensive. This requirement is relaxed by using the restarted GMRES 1539 * method which puts a cap on the number of vectors we are required to store 1540 * at any one time (here we restart after 50 temporary vectors, or 48 1541 * iterations). This then has the disadvantage that we lose information we 1542 * have gathered throughout the iteration and therefore we could see slower 1543 * convergence. As a consequence, where to restart is a question of balancing 1544 * memory consumption, CPU effort, and convergence speed. 1545 * However, the goal of this tutorial is to have very low 1546 * iteration counts by using a powerful GMG preconditioner, so we have picked 1547 * the restart length such that all of the results shown below converge prior 1548 * to restart happening, and thus we have a standard GMRES method. If the user 1549 * is interested, another suitable method offered in deal.II would be 1556 * template <int dim> 1557 * void AdvectionProblem<dim>::solve() 1559 * const unsigned int max_iters = 200; 1560 * const double solve_tolerance = 1e-8 * system_rhs.l2_norm(); 1561 * SolverControl solver_control(max_iters, solve_tolerance, true, true); 1562 * solver_control.enable_history_data(); 1564 * using Transfer = MGTransferPrebuilt<Vector<double>>; 1565 * Transfer mg_transfer(mg_constrained_dofs); 1566 * mg_transfer.build(dof_handler); 1568 * FullMatrix<double> coarse_matrix; 1569 * coarse_matrix.copy_from(mg_matrices[0]); 1570 * MGCoarseGridHouseholder<double, Vector<double>> coarse_grid_solver; 1571 * coarse_grid_solver.initialize(coarse_matrix); 1575 * mg_matrix.initialize(mg_matrices); 1576 * mg_interface_matrix_in.initialize(mg_interface_in); 1577 * mg_interface_matrix_out.initialize(mg_interface_out); 1579 * Multigrid<Vector<double>> mg( 1580 * mg_matrix, coarse_grid_solver, mg_transfer, *mg_smoother, *mg_smoother); 1581 * mg.set_edge_matrices(mg_interface_matrix_out, mg_interface_matrix_in); 1583 * PreconditionMG<dim, Vector<double>, Transfer> preconditioner(dof_handler, 1587 * std::cout << " Solving with GMRES to tol " << solve_tolerance << "..." 1589 * SolverGMRES<Vector<double>> solver( 1590 * solver_control, SolverGMRES<Vector<double>>::AdditionalData(50, true)); 1594 * solver.solve(system_matrix, solution, system_rhs, preconditioner); 1597 * std::cout << " converged in " << solver_control.last_step() 1599 * << " in " << time.last_wall_time() << " seconds " << std::endl; 1601 * constraints.distribute(solution); 1603 * mg_smoother.release(); 1610 * <a name="codeAdvectionProblemoutput_resultscode"></a> 1611 * <h4><code>AdvectionProblem::output_results()</code></h4> 1615 * The final function of interest generates graphical output. 1616 * Here we output the solution and cell ordering in a .vtu format. 1620 * At the top of the function, we generate an index for each cell to 1621 * visualize the ordering used by the smoothers. Note that we do 1622 * this only for the active cells instead of the levels, where the 1623 * smoothers are actually used. For the point smoothers we renumber 1624 * DoFs instead of cells, so this is only an approximation of what 1625 * happens in reality. Finally, the random ordering is not the 1626 * random ordering we actually use (see `create_smoother()` for that). 1630 * The (integer) ordering of cells is then copied into a (floating 1631 * point) vector for graphical output. 1634 * template <int dim> 1635 * void AdvectionProblem<dim>::output_results(const unsigned int cycle) const 1637 * const unsigned int n_active_cells = triangulation.n_active_cells(); 1638 * Vector<double> cell_indices(n_active_cells); 1640 * std::vector<unsigned int> ordered_indices; 1641 * switch (settings.dof_renumbering) 1643 * case Settings::DoFRenumberingStrategy::downstream: 1645 * create_downstream_cell_ordering(dof_handler, advection_direction); 1648 * case Settings::DoFRenumberingStrategy::upstream: 1650 * create_downstream_cell_ordering(dof_handler, 1651 * -1.0 * advection_direction); 1654 * case Settings::DoFRenumberingStrategy::random: 1655 * ordered_indices = create_random_cell_ordering(dof_handler); 1658 * case Settings::DoFRenumberingStrategy::none: 1659 * ordered_indices.resize(n_active_cells); 1660 * for (unsigned int i = 0; i < n_active_cells; ++i) 1661 * ordered_indices[i] = i; 1665 * AssertThrow(false, ExcNotImplemented()); 1669 * for (unsigned int i = 0; i < n_active_cells; ++i) 1670 * cell_indices(ordered_indices[i]) = static_cast<double>(i); 1675 * The remainder of the function is then straightforward, given 1676 * previous tutorial programs: 1679 * DataOut<dim> data_out; 1680 * data_out.attach_dof_handler(dof_handler); 1681 * data_out.add_data_vector(solution, "solution"); 1682 * data_out.add_data_vector(cell_indices, "cell_index"); 1683 * data_out.build_patches(); 1685 * const std::string filename = 1686 * "solution-" + Utilities::int_to_string(cycle) + ".vtu"; 1687 * std::ofstream output(filename.c_str()); 1688 * data_out.write_vtu(output); 1695 * <a name="codeAdvectionProblemruncode"></a> 1696 * <h4><code>AdvectionProblem::run()</code></h4> 1700 * As in most tutorials, this function creates/refines the mesh and calls 1701 * the various functions defined above to set up, assemble, solve, and output 1706 * In cycle zero, we generate the mesh for the on the square 1707 * <code>[-1,1]^dim</code> with a hole of radius 3/10 units centered 1708 * at the origin. For objects with `manifold_id` equal to one 1709 * (namely, the faces adjacent to the hole), we assign a spherical 1716 * template <int dim> 1717 * void AdvectionProblem<dim>::run() 1719 * for (unsigned int cycle = 0; cycle < (settings.fe_degree == 1 ? 7 : 5); 1722 * std::cout << " Cycle " << cycle << ':
' << std::endl; 1726 * GridGenerator::hyper_cube_with_cylindrical_hole(triangulation, 1730 * const SphericalManifold<dim> manifold_description(Point<dim>(0, 0)); 1731 * triangulation.set_manifold(1, manifold_description); 1734 * triangulation.refine_global(); 1738 * std::cout << " Number of active cells: " 1739 * << triangulation.n_active_cells() << " (" 1740 * << triangulation.n_levels() << " levels)" << std::endl; 1741 * std::cout << " Number of degrees of freedom: " 1742 * << dof_handler.n_dofs() << std::endl; 1744 * assemble_system_and_multigrid(); 1748 * if (settings.output) 1749 * output_results(cycle); 1751 * std::cout << std::endl; 1754 * } // namespace Step63 1760 * <a name="Thecodemaincodefunction"></a> 1761 * <h3>The <code>main</code> function</h3> 1765 * Finally, the main function is like most tutorials. The only 1766 * interesting bit is that we require the user to pass a `.prm` file 1767 * as a sole command line argument. If no parameter file is given, the 1768 * program will output the contents of a sample parameter file with 1769 * all default values to the screen that the user can then copy and 1770 * paste into their own `.prm` file. 1776 * int main(int argc, char *argv[]) 1780 * Step63::Settings settings; 1781 * settings.get_parameters((argc > 1) ? (argv[1]) : ""); 1783 * Step63::AdvectionProblem<2> advection_problem_2d(settings); 1784 * advection_problem_2d.run(); 1786 * catch (std::exception &exc) 1788 * std::cerr << std::endl 1790 * << "----------------------------------------------------" 1792 * std::cerr << "Exception on processing: " << std::endl 1793 * << exc.what() << std::endl 1794 * << "Aborting!" << std::endl 1795 * << "----------------------------------------------------" 1801 * std::cerr << std::endl 1803 * << "----------------------------------------------------" 1805 * std::cerr << "Unknown exception!" << std::endl 1806 * << "Aborting!" << std::endl 1807 * << "----------------------------------------------------" 1815 <a name="Results"></a><h1>Results</h1> 1818 <a name="GMRESIterationNumbers"></a><h3> GMRES Iteration Numbers </h3> 1821 The major advantage for GMG is that it is an @f$\mathcal{O}(n)@f$ method, 1822 that is, the complexity of the problem increases linearly with the 1823 problem size. To show then that the linear solver presented in this 1824 tutorial is in fact @f$\mathcal{O}(n)@f$, all one needs to do is show that 1825 the iteration counts for the GMRES solve stay roughly constant as we 1828 Each of the following tables gives the GMRES iteration counts to reduce the 1829 initial residual by a factor of @f$10^8@f$. We selected a sufficient number of smoothing steps 1830 (based on the method) to get iteration numbers independent of mesh size. As 1831 can be seen from the tables below, the method is indeed @f$\mathcal{O}(n)@f$. 1833 <a name="DoFCellRenumbering"></a><h4> DoF/Cell Renumbering </h4> 1836 The point-wise smoothers ("Jacobi" and "SOR") get applied in the order the 1837 DoFs are numbered on each level. We can influence this using the 1838 DoFRenumbering namespace. The block smoothers are applied based on the 1839 ordering we set in `setup_smoother()`. We can visualize this numbering. The 1840 following pictures show the cell numbering of the active cells in downstream, 1841 random, and upstream numbering (left to right): 1843 <img src="https://www.dealii.org/images/steps/developer/step-63-cell-order.png" alt=""> 1845 Let us start with the additive smoothers. The following table shows 1846 the number of iterations necessary to obtain convergence from GMRES: 1848 <table align="center" class="doxtable"> 1852 <th colspan="1">@f$Q_1@f$</th> 1853 <th colspan="7">Smoother (smoothing steps)</th> 1859 <th colspan="3">Jacobi (6)</th> 1861 <th colspan="3">Block Jacobi (3)</th> 1867 <th colspan="3">Renumbering Strategy</th> 1869 <th colspan="3">Renumbering Strategy</th> 1969 We see that renumbering the 1970 DoFs/cells has no effect on convergence speed. This is because these 1971 smoothers compute operations on each DoF (point-smoother) or cell 1972 (block-smoother) independently and add up the results. Since we can 1973 define these smoothers as an application of a sum of matrices, and 1974 matrix addition is commutative, the order at which we sum the 1975 different components will not affect the end result. 1977 On the other hand, the situation is different for multiplicative smoothers: 1979 <table align="center" class="doxtable"> 1983 <th colspan="1">@f$Q_1@f$</th> 1984 <th colspan="7">Smoother (smoothing steps)</th> 1990 <th colspan="3">SOR (3)</th> 1992 <th colspan="3">Block SOR (1)</th> 1998 <th colspan="3">Renumbering Strategy</th> 2000 <th colspan="3">Renumbering Strategy</th> 2100 Here, we can speed up 2101 convergence by renumbering the DoFs/cells in the advection direction, 2102 and similarly, we can slow down convergence if we do the renumbering 2103 in the opposite direction. This is because advection-dominated 2104 problems have a directional flow of information (in the advection 2105 direction) which, given the right renumbering of DoFs/cells, 2106 multiplicative methods are able to capture. 2108 This feature of multiplicative methods is, however, dependent on the 2109 value of @f$\varepsilon@f$. As we increase @f$\varepsilon@f$ and the problem 2110 becomes more diffusion-dominated, we have a more uniform propagation 2111 of information over the mesh and there is a diminished advantage for 2112 renumbering in the advection direction. On the opposite end, in the 2113 extreme case of @f$\varepsilon=0@f$ (advection-only), we have a 1st-order 2114 PDE and multiplicative methods with the right renumbering become 2115 effective solvers: A correct downstream numbering may lead to methods 2116 that require only a single iteration because information can be 2117 propagated from the inflow boundary downstream, with no information 2118 transport in the opposite direction. (Note, however, that in the case 2119 of @f$\varepsilon=0@f$, special care must be taken for the boundary 2120 conditions in this case). 2123 <a name="Pointvsblocksmoothers"></a><h4> %Point vs. block smoothers </h4> 2126 We will limit the results to runs using the downstream 2127 renumbering. Here is a cross comparison of all four smoothers for both 2128 @f$Q_1@f$ and @f$Q_3@f$ elements: 2130 <table align="center" class="doxtable"> 2134 <th colspan="1">@f$Q_1@f$</th> 2135 <th colspan="4">Smoother (smoothing steps)</th> 2137 <th colspan="1">@f$Q_3@f$</th> 2138 <th colspan="4">Smoother (smoothing steps)</th> 2141 <th colspan="1">Cells</th> 2143 <th colspan="1">DoFs</th> 2144 <th colspan="1">Jacobi (6)</th> 2145 <th colspan="1">Block Jacobi (3)</th> 2146 <th colspan="1">SOR (3)</th> 2147 <th colspan="1">Block SOR (1)</th> 2149 <th colspan="1">DoFs</th> 2150 <th colspan="1">Jacobi (6)</th> 2151 <th colspan="1">Block Jacobi (3)</th> 2152 <th colspan="1">SOR (3)</th> 2153 <th colspan="1">Block SOR (1)</th> 2252 We see that for @f$Q_1@f$, both multiplicative smoothers require a smaller 2253 combination of smoothing steps and iteration counts than either 2254 additive smoother. However, when we increase the degree to a @f$Q_3@f$ 2255 element, there is a clear advantage for the block smoothers in terms 2256 of the number of smoothing steps and iterations required to 2257 solve. Specifically, the block SOR smoother gives constant iteration 2258 counts over the degree, and the block Jacobi smoother only sees about 2259 a 38% increase in iterations compared to 75% and 183% for Jacobi and 2262 <a name="Cost"></a><h3> Cost </h3> 2265 Iteration counts do not tell the full story in the optimality of a one 2266 smoother over another. Obviously we must examine the cost of an 2267 iteration. Block smoothers here are at a disadvantage as they are 2268 having to construct and invert a cell matrix for each cell. Here is a 2269 comparison of solve times for a @f$Q_3@f$ element with 74,496 DoFs: 2271 <table align="center" class="doxtable"> 2273 <th colspan="1">@f$Q_3@f$</th> 2274 <th colspan="4">Smoother (smoothing steps)</th> 2277 <th colspan="1">DoFs</th> 2278 <th colspan="1">Jacobi (6)</th> 2279 <th colspan="1">Block Jacobi (3)</th> 2280 <th colspan="1">SOR (3)</th> 2281 <th colspan="1">Block SOR (1)</th> 2292 The smoother that requires the most iterations (Jacobi) actually takes 2293 the shortest time (roughly 2/3 the time of the next fastest 2294 method). This is because all that is required to apply a Jacobi 2295 smoothing step is multiplication by a diagonal matrix which is very 2296 cheap. On the other hand, while SOR requires over 3x more iterations 2297 (each with 3x more smoothing steps) than block SOR, the times are 2298 roughly equivalent, implying that a smoothing step of block SOR is 2299 roughly 9x slower than a smoothing step of SOR. Lastly, block Jacobi 2300 is almost 6x more expensive than block SOR, which intuitively makes 2301 sense from the fact that 1 step of each method has the same cost 2302 (inverting the cell matrices and either adding or multiply them 2303 together), and block Jacobi has 3 times the number of smoothing steps per 2304 iteration with 2 times the iterations. 2307 <a name="Additionalpoints"></a><h3> Additional points </h3> 2310 There are a few more important points to mention: 2313 <li> For a mesh distributed in parallel, multiplicative methods cannot 2314 be executed over the entire domain. This is because they operate one 2315 cell at a time, and downstream cells can only be handled once upstream 2316 cells have already been done. This is fine on a single processor: The 2317 processor just goes through the list of cells one after the 2318 other. However, in parallel, it would imply that some processors are 2319 idle because upstream processors have not finished doing the work on 2320 cells upstream from the ones owned by the current processor. Once the 2321 upstream processors are done, the downstream ones can start, but by 2322 that time the upstream processors have no work left. In other words, 2323 most of the time during these smoother steps, most processors are in 2324 fact idle. This is not how one obtains good parallel scalability! 2326 One can use a hybrid method where 2327 a multiplicative smoother is applied on each subdomain, but as you 2328 increase the number of subdomains, the method approaches the behavior 2329 of an additive method. This is a major disadvantage to these methods. 2332 <li> Current research into block smoothers suggest that soon we will be 2333 able to compute the inverse of the cell matrices much cheaper than 2334 what is currently being done inside deal.II. This research is based on 2335 the fast diagonalization method (dating back to the 1960s) and has 2336 been used in the spectral community for around 20 years (see, e.g., <a 2337 href="https://doi.org/10.1007/s10915-004-4787-3"> Hybrid 2338 Multigrid/Schwarz Algorithms for the Spectral Element Method by Lottes 2339 and Fischer</a>). There are currently efforts to generalize these 2340 methods to DG and make them more robust. Also, it seems that one 2341 should be able to take advantage of matrix-free implementations and 2342 the fact that, in the interior of the domain, cell matrices tend to 2343 look very similar, allowing fewer matrix inverse computations. 2347 Combining 1. and 2. gives a good reason for expecting that a method 2348 like block Jacobi could become very powerful in the future, even 2349 though currently for these examples it is quite slow. 2352 <a name="Possibilitiesforextensions"></a><h3> Possibilities for extensions </h3> 2355 <a name="ConstantiterationsforQsub5sub"></a><h4> Constant iterations for Q<sub>5</sub> </h4> 2358 Change the number of smoothing steps and the smoother relaxation 2359 parameter (set in <code>Smoother::AdditionalData()</code> inside 2360 <code>create_smoother()</code>, only necessary for point smoothers) so 2361 that we maintain a constant number of iterations for a @f$Q_5@f$ element. 2363 <a name="Effectivenessofrenumberingforchangingepsilon"></a><h4> Effectiveness of renumbering for changing epsilon </h4> 2366 Increase/decrease the parameter "Epsilon" in the `.prm` files of the 2367 multiplicative methods and observe for which values renumbering no 2368 longer influences convergence speed. 2370 <a name="Meshadaptivity"></a><h4> Mesh adaptivity </h4> 2373 The code is set up to work correctly with an adaptively refined mesh (the 2374 interface matrices are created and set). Devise a suitable refinement 2375 criterium or try the KellyErrorEstimator class. 2381 <a name="PlainProg"></a> 2382 <h1> The plain program</h1> 2383 @include "step-63.cc" inline ::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &x)
long int get_integer(const std::string &entry_string) const
inline ::VectorizedArray< Number, width > exp(const ::VectorizedArray< Number, width > &x)
void downstream(DoFHandlerType &dof_handler, const Tensor< 1, DoFHandlerType::space_dimension > &direction, const bool dof_wise_renumbering=false)
Contents is actually a matrix.
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
std::string get(const std::string &entry_string) const
std::ostream & print_parameters(std::ostream &out, const OutputStyle style) const
static const types::blas_int one
#define AssertThrow(cond, exc)
static ::ExceptionBase & ExcIndexRange(int arg1, int arg2, int arg3)
void make_hanging_node_constraints(const DoFHandlerType &dof_handler, AffineConstraints< number > &constraints)
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > epsilon(const Tensor< 2, dim, Number > &Grad_u)
void mesh_loop(const CellIteratorType &begin, const typename identity< CellIteratorType >::type &end, const typename identity< std::function< void(const CellIteratorBaseType &, ScratchData &, CopyData &)>>::type &cell_worker, const typename identity< std::function< void(const CopyData &)>>::type &copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const AssembleFlags flags=assemble_own_cells, const typename identity< std::function< void(const CellIteratorBaseType &, const unsigned int, ScratchData &, CopyData &)>>::type &boundary_worker=std::function< void(const CellIteratorBaseType &, const unsigned int, ScratchData &, CopyData &)>(), const typename identity< std::function< void(const CellIteratorBaseType &, const unsigned int, const unsigned int, const CellIteratorBaseType &, const unsigned int, const unsigned int, ScratchData &, CopyData &)>>::type &face_worker=std::function< void(const CellIteratorBaseType &, const unsigned int, const unsigned int, const CellIteratorBaseType &, const unsigned int, const unsigned int, ScratchData &, CopyData &)>(), const unsigned int queue_length=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
void random(DoFHandlerType &dof_handler)
static ::ExceptionBase & ExcMessage(std::string arg1)
double get_double(const std::string &entry_name) const
void declare_entry(const std::string &entry, const std::string &default_value, const Patterns::PatternBase &pattern=Patterns::Anything(), const std::string &documentation="", const bool has_to_be_set=false)
#define Assert(cond, exc)
static ::ExceptionBase & ExcDimensionMismatch(std::size_t arg1, std::size_t arg2)
Expression coth(const Expression &x)
virtual void parse_input(std::istream &input, const std::string &filename="input file", const std::string &last_line="", const bool skip_undefined=false)
bool get_bool(const std::string &entry_name) const
Expression fabs(const Expression &x)
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
void assemble(const MeshWorker::DoFInfoBox< dim, DOFINFO > &dinfo, A *assembler)
static constexpr double PI
virtual void value_list(const std::vector< Point< dim >> &points, std::vector< RangeNumberType > &values, const unsigned int component=0) const
constexpr Number trace(const SymmetricTensor< 2, dim2, Number > &)
static ::ExceptionBase & ExcNotImplemented()
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)
static const types::blas_int zero
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
virtual RangeNumberType value(const Point< dim > &p, const unsigned int component=0) const
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)
inline ::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &x)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
int(&) functions(const void *v1, const void *v2)