Fluid structure interaction suite
poisson.cc
Go to the documentation of this file.
1 // ---------------------------------------------------------------------
2 //
3 // Copyright (C) 2022 by Luca Heltai
4 //
5 // This file is part of the FSI-suite platform, based on the deal.II library.
6 //
7 // The FSI-suite platform is free software; you can use it, redistribute it,
8 // and/or modify it under the terms of the GNU Lesser General Public License as
9 // published by the Free Software Foundation; either version 3.0 of the License,
10 // or (at your option) any later version. The full text of the license can be
11 // found in the file LICENSE at the top level of the FSI-suite platform
12 // distribution.
13 //
14 // ---------------------------------------------------------------------
15 
16 #include "pdes/serial/poisson.h"
17 
19 
21 
22 
23 using namespace dealii;
24 namespace PDEs
25 {
26  namespace Serial
27  {
28  template <int dim, int spacedim>
30  : ParameterAcceptor("/Poisson/")
31  , grid_generator("/Poisson/Grid")
32  , grid_refinement("/Poisson/Grid/Refinement")
33  , finite_element("/Poisson/", "u", "FE_Q(1)")
34  , dof_handler(triangulation)
35  , inverse_operator("/Poisson/Solver")
36  , preconditioner("/Poisson/Solver/AMG Preconditioner")
37  , constants("/Poisson/Constants",
38  {"kappa"},
39  {1.0},
40  {"Diffusion coefficient"})
41  , forcing_term("/Poisson/Functions",
42  "kappa*8*PI^2*sin(2*PI*x)*sin(2*PI*y)",
43  "Forcing term")
44  , exact_solution("/Poisson/Functions",
45  "sin(2*PI*x)*sin(2*PI*y)",
46  "Exact solution")
47  , boundary_conditions("/Poisson/Boundary conditions")
48  , error_table("/Poisson/Error table")
49  , data_out("/Poisson/Output")
50  {
51  add_parameter("Console level", this->console_level);
52  }
53 
54 
55 
56  template <int dim, int spacedim>
57  void
59  {
60  deallog << "System setup" << std::endl;
61  // No mixed grids
62  const auto ref_cells = triangulation.get_reference_cells();
64  ref_cells.size() == 1,
65  ExcMessage(
66  "This program does nots support mixed simplx/hex grid types."));
67 
68  // Compatible FE space and grid.
69  AssertThrow(finite_element().reference_cell() == ref_cells[0],
70  ExcMessage("The finite element must be defined on the same "
71  "cell type as the grid."));
72 
73  // We propagate the information about the constants to all functions of
74  // the problem, so that constants can be used within the functions
75  boundary_conditions.update_user_substitution_map(constants);
76  exact_solution.update_constants(constants);
77  forcing_term.update_constants(constants);
78 
79  dof_handler.distribute_dofs(finite_element);
80 
81  // Since our code runs both for simplex grids and for hyper-cube grids, we
82  // need to make sure that we build the correct mapping for the grid. In
83  // this code we actually use a linear mapping, independently on the order
84  // of the finite element space.
85  mapping = get_default_linear_mapping(triangulation).clone();
86 
87  deallog << "Number of dofs " << dof_handler.n_dofs() << std::endl;
88 
89  constraints.clear();
90  DoFTools::make_hanging_node_constraints(dof_handler, constraints);
91 
92  // We now check that the boundary conditions are consistent with the
93  // triangulation object, that is, we check that the boundary indicators
94  // specified in the parameter file are actually present in the
95  // triangulation, that no boundary indicator is specified twice, and that
96  // all boundary ids of the triangulation are actually covered by the
97  // parameter file configuration.
98  boundary_conditions.check_consistency(triangulation);
99 
100  // This is where we apply essential boundary conditions. The
101  // ParsedTools::BoundaryConditions class takes care of collecting boundary
102  // ids, and calling the appropriate function to apply the boundary
103  // condition on the selected boundary ids. Essential boundary conditions
104  // need to be incorporated in the constraints of the linear system, since
105  // they are part of the definition of the solution space.
106  //
107  // Natural bondary conditions, on the other hand, need access to the rhs
108  // of the problem and are not treated via constraints. We will deal with
109  // them later on.
110  boundary_conditions.apply_essential_boundary_conditions(*mapping,
111  dof_handler,
112  constraints);
113  constraints.close();
114 
115  // Everything here is identical to step-3 in the deal.II tutorials.
116  DynamicSparsityPattern dsp(dof_handler.n_dofs());
117  DoFTools::make_sparsity_pattern(dof_handler, dsp, constraints, false);
118  sparsity_pattern.copy_from(dsp);
119  system_matrix.reinit(sparsity_pattern);
120  solution.reinit(dof_handler.n_dofs());
121  system_rhs.reinit(dof_handler.n_dofs());
122 
123  // And here ParsedTools::BoundaryConditions kicks in again, and allows you
124  // to assemble those boundary condition types that require access to the
125  // matrix and to the rhs, after the essential boundary conditions are
126  // taken care of.
127  boundary_conditions.apply_natural_boundary_conditions(
128  *mapping, dof_handler, constraints, system_matrix, system_rhs);
129  }
130 
131 
132 
133  template <int dim, int spacedim>
134  void
136  {
137  deallog << "Assemble system" << std::endl;
138  const ReferenceCell cell_type = finite_element().reference_cell();
139 
140  // Differently from step-3, here we need to change quadrature formula
141  // according to the type of cell that the triangulation stores. If it is
142  // quads or hexes, then this would be QGauss, otherwise, it will be
143  // QGaussSimplex.
144  const Quadrature<dim> quadrature_formula =
145  cell_type.get_gauss_type_quadrature<dim>(
146  finite_element().tensor_degree() + 1);
147 
148  // Again, since we support also tria and tets, we need to make sure we
149  // pass a compatible mapping to the FEValues object, otherwise things may
150  // not work.
151  FEValues<dim, spacedim> fe_values(*mapping,
152  finite_element,
153  quadrature_formula,
157 
158  const unsigned int dofs_per_cell = finite_element().n_dofs_per_cell();
159  FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
160  Vector<double> cell_rhs(dofs_per_cell);
161  std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
162  for (const auto &cell : dof_handler.active_cell_iterators())
163  {
164  fe_values.reinit(cell);
165  cell_matrix = 0;
166  cell_rhs = 0;
167  for (const unsigned int q_index :
168  fe_values.quadrature_point_indices())
169  {
170  for (const unsigned int i : fe_values.dof_indices())
171  for (const unsigned int j : fe_values.dof_indices())
172  cell_matrix(i, j) +=
173  (constants["kappa"] *
174  fe_values.shape_grad(i, q_index) * // grad phi_i(x_q)
175  fe_values.shape_grad(j, q_index) * // grad phi_j(x_q)
176  fe_values.JxW(q_index)); // dx
177  for (const unsigned int i : fe_values.dof_indices())
178  cell_rhs(i) +=
179  (fe_values.shape_value(i, q_index) * // phi_i(x_q)
180  forcing_term.value(
181  fe_values.quadrature_point(q_index)) * // f(x_q)
182  fe_values.JxW(q_index)); // dx
183  }
184 
185  cell->get_dof_indices(local_dof_indices);
186  constraints.distribute_local_to_global(cell_matrix,
187  cell_rhs,
188  local_dof_indices,
189  system_matrix,
190  system_rhs);
191  }
192  }
193 
194 
195  // We use a different approach for the solution of the linear system here
196  // w.r.t. what is done in step-3. The ParsedLAC::InverseOperator object is
197  // used to infer the solver type and the solver parameters from the
198  // parameter file, and the same is done for an AMG preconditioner.
199  template <int dim, int spacedim>
200  void
202  {
203  deallog << "Solve system" << std::endl;
204  preconditioner.initialize(system_matrix);
205  const auto A = linear_operator<Vector<double>>(system_matrix);
206  const auto Ainv = inverse_operator(A, preconditioner);
207  solution = Ainv * system_rhs;
208  constraints.distribute(solution);
209  }
210 
211 
212 
213  // Finally, we output the solution to a file in a format that can be
214  // speficied in the parameter file. The structure of this function is
215  // identical to step-3, however, here we use the ParsedTools::DataOut class,
216  // which specifies the output format, the filename, and many other options
217  // from the parameter file, and just add the solution vector using the
218  // ParsedTools::DataOut::add_data_vector() function, which works similarly
219  // to dealii::DataOut::add_data_vector(), with the exception of the second
220  // argument. Here the type of data to produce (i.e., vector, scalar, etc.)
221  // is inferred by the repetitions in the component_names arguement, rather
222  // than specifying them by hand.
223  //
224  // This makes the structure of this function almost identical in all
225  // programs of the FSI-suite.
226  template <int dim, int spacedim>
227  void
228  Poisson<dim, spacedim>::output_results(const unsigned cycle) const
229  {
230  deallog << "Output results" << std::endl;
231  // Save each cycle in its own file
232  const auto suffix =
235  grid_refinement.get_n_refinement_cycles()));
236  data_out.attach_dof_handler(dof_handler, suffix);
237  data_out.add_data_vector(solution, component_names);
238  data_out.write_data_and_clear(*mapping);
239  }
240 
241 
242  // And finally, the run() method. This is the main function of the program.
243  // Differently from what is done in step-3, we don't have a separate
244  // function for the grid generation, since we let the grid_generator object
245  // do the heavy lifting. And differently from what happens in step-3, here
246  // we also do several cycles of refinement, in order to compute the
247  // convergence rates of the error.
248  template <int dim, int spacedim>
249  void
251  {
252  deallog.pop();
253  deallog.depth_console(console_level);
254  grid_generator.generate(triangulation);
255  for (const auto &cycle : grid_refinement.get_refinement_cycles())
256  {
257  deallog.push("Cycle " + Utilities::int_to_string(cycle));
258  setup_system();
259  assemble_system();
260  solve();
261 
262  // This is is leveraging the ParsedTools::ConvergenceTable class, to
263  // compute errors according to what is specified in the parameter file
264  error_table.error_from_exact(dof_handler, solution, exact_solution);
265  output_results(cycle);
266 
267  // Differently from step-3, we also support local refinement. The
268  // following function call is only executed if we are not in the last
269  // cycle, and implements the actual estimate, mark, refine steps of
270  // classical AFEM algorithms, using the parameter file to drive the
271  // execution.
272  if (cycle < grid_refinement.get_n_refinement_cycles() - 1)
273  grid_refinement.estimate_mark_refine(*mapping,
274  dof_handler,
275  solution,
276  triangulation);
277  deallog.pop();
278  }
279  // Make sure we output the error table after the last cycle
280  error_table.output_table(std::cout);
281  }
282 
283  // We explicitly instantiate all of the different combinations of dim and
284  // spacedim, so that users can run this in different dimension in the tests
285  template class Poisson<1, 1>;
286  template class Poisson<1, 2>;
287  template class Poisson<1, 3>;
288  template class Poisson<2, 2>;
289  template class Poisson<2, 3>;
290  template class Poisson<3, 3>;
291  } // namespace Serial
292 } // namespace PDEs
void push(const std::string &text)
unsigned int depth_console(const unsigned int n)
void pop()
Quadrature< dim > get_gauss_type_quadrature(const unsigned n_points_1d) const
Poisson problem, serial version.
Definition: poisson.h:193
void solve()
Solve the linear system generated in the assemble_system() function.
Definition: poisson.cc:201
void assemble_system()
Assemble the matrix and right hand side vector.
Definition: poisson.cc:135
void run()
Main entry point of the program.
Definition: poisson.cc:250
void setup_system()
Setup dofs, constraints, and matrices.
Definition: poisson.cc:58
void output_results(const unsigned cycle) const
Output the solution.
Definition: poisson.cc:228
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
LinearOperator< Domain, Range, Payload > inverse_operator(const LinearOperator< Range, Domain, Payload > &op, Solver &solver, const Preconditioner &preconditioner)
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_values
update_JxW_values
update_gradients
update_quadrature_points
LogStream deallog
const Mapping< dim, spacedim > & get_default_linear_mapping(const Triangulation< dim, spacedim > &triangulation)
void reference_cell(Triangulation< dim, spacedim > &tria, const ReferenceCell &reference_cell)
std::string int_to_string(const unsigned int value, const unsigned int digits=numbers::invalid_unsigned_int)
unsigned int needed_digits(const unsigned int max_number)
We collect in this namespace all PDEs that are relevant to Fluid Structure Interaction Problems.
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation