Fluid structure interaction suite
poisson_nitsche_interface.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 
17 
18 #include <deal.II/grid/grid_out.h>
19 
21 
23 
24 
25 using namespace dealii;
26 namespace PDEs
27 {
28  namespace Serial
29  {
30  template <int dim, int spacedim>
32  : ParameterAcceptor("/PoissonNitscheInterface/")
33  , grid_generator("/PoissonNitscheInterface/Grid/Ambient")
34  , embedded_grid_generator("/PoissonNitscheInterface/Grid/Embedded")
35  , grid_refinement("/PoissonNitscheInterface/Grid/Refinement")
36  , space_fe("/PoissonNitscheInterface/", "u", "FE_Q(1)")
37  , space_dh(space_triangulation)
38  , inverse_operator("/PoissonNitscheInterface/Solver")
39  , preconditioner("/PoissonNitscheInterface/Solver/AMG Preconditioner")
40  , constants("/PoissonNitscheInterface/Constants",
41  {"kappa"},
42  {1.0},
43  {"Diffusion coefficient"})
44  , forcing_term("/PoissonNitscheInterface/Functions",
45  "kappa*8*PI^2*sin(2*PI*x)*sin(2*PI*y)",
46  "Forcing term")
47  , embedded_value("/PoissonNitscheInterface/Functions",
48  "1.0",
49  "Embedded value")
50  , nitsche_coefficient("/PoissonNitscheInterface/Functions",
51  "2.0",
52  "Nitsche cofficient")
53  , exact_solution("/PoissonNitscheInterface/Functions",
54  "sin(2*PI*x)*sin(2*PI*y)",
55  "Exact solution")
56  , boundary_conditions("/PoissonNitscheInterface/Boundary conditions")
57  , timer(deallog.get_console(),
60  , error_table("/PoissonNitscheInterface/Error table")
61  , data_out("/PoissonNitscheInterface/Output")
62  {
63  add_parameter("Console level", this->console_level);
64  }
65 
66 
67 
68  template <int dim, int spacedim>
69  void
71  {
72  TimerOutput::Scope timer_section(timer, "Generate grids");
73  grid_generator.generate(space_triangulation);
74  embedded_grid_generator.generate(embedded_triangulation);
75  // We create unique pointers to cached triangulations. This This objects
76  // will be necessary to compute the the Quadrature formulas on the
77  // intersection of the cells.
78  space_cache = std::make_unique<GridTools::Cache<spacedim, spacedim>>(
79  space_triangulation);
80  embedded_cache = std::make_unique<GridTools::Cache<dim, spacedim>>(
81  embedded_triangulation);
82  }
83 
84 
85 
86  template <int dim, int spacedim>
87  void
89  {
90  TimerOutput::Scope timer_section(timer, "Setup system");
91  deallog << "System setup" << std::endl;
92  // No mixed grids
93  const auto ref_cells = space_triangulation.get_reference_cells();
95  ref_cells.size() == 1,
96  ExcMessage(
97  "This program does nots support mixed simplx/hex grid types."));
98 
99  // Compatible FE space and grid.
100  AssertThrow(space_fe().reference_cell() == ref_cells[0],
101  ExcMessage("The finite element must be defined on the same "
102  "cell type as the grid."));
103 
104  // We propagate the information about the constants to all functions of
105  // the problem, so that constants can be used within the functions
106  boundary_conditions.update_user_substitution_map(constants);
107  exact_solution.update_constants(constants);
108  forcing_term.update_constants(constants);
109  embedded_value.update_constants(constants);
110  space_dh.distribute_dofs(space_fe);
111 
112 
113  mapping = get_default_linear_mapping(space_triangulation).clone();
114 
115  deallog << "Number of dofs " << space_dh.n_dofs() << std::endl;
116 
117  space_constraints.clear();
118  DoFTools::make_hanging_node_constraints(space_dh, space_constraints);
119 
120 
121  boundary_conditions.check_consistency(space_triangulation);
122 
123  // This is where we apply essential boundary conditions.
124  boundary_conditions.apply_essential_boundary_conditions(
125  *mapping, space_dh, space_constraints);
126  space_constraints.close();
127 
128 
129  DynamicSparsityPattern dsp(space_dh.n_dofs());
130  DoFTools::make_sparsity_pattern(space_dh, dsp, space_constraints, false);
131  sparsity_pattern.copy_from(dsp);
132  system_matrix.reinit(sparsity_pattern);
133  solution.reinit(space_dh.n_dofs());
134  system_rhs.reinit(space_dh.n_dofs());
135  }
136 
137 
138 
139  template <int dim, int spacedim>
140  void
142  {
143  {
144  TimerOutput::Scope timer_section(timer, "Assemble system");
145  deallog << "Assemble system" << std::endl;
146  const ReferenceCell cell_type = space_fe().reference_cell();
147 
148 
149  const Quadrature<spacedim> quadrature_formula =
150  cell_type.get_gauss_type_quadrature<spacedim>(
151  space_fe().tensor_degree() + 1);
152 
153 
154  FEValues<spacedim, spacedim> fe_values(*mapping,
155  space_fe,
156  quadrature_formula,
157  update_values |
161 
162  const unsigned int dofs_per_cell = space_fe().n_dofs_per_cell();
163  FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
164  Vector<double> cell_rhs(dofs_per_cell);
165  std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
166  for (const auto &cell : space_dh.active_cell_iterators())
167  {
168  fe_values.reinit(cell);
169  cell_matrix = 0;
170  cell_rhs = 0;
171  for (const unsigned int q_index :
172  fe_values.quadrature_point_indices())
173  {
174  for (const unsigned int i : fe_values.dof_indices())
175  for (const unsigned int j : fe_values.dof_indices())
176  cell_matrix(i, j) +=
177  (constants["kappa"] *
178  fe_values.shape_grad(i, q_index) * // grad phi_i(x_q)
179  fe_values.shape_grad(j, q_index) * // grad phi_j(x_q)
180  fe_values.JxW(q_index)); // dx
181  for (const unsigned int i : fe_values.dof_indices())
182  cell_rhs(i) +=
183  (fe_values.shape_value(i, q_index) * // phi_i(x_q)
184  forcing_term.value(
185  fe_values.quadrature_point(q_index)) * // f(x_q)
186  fe_values.JxW(q_index)); // dx
187  }
188 
189  cell->get_dof_indices(local_dof_indices);
190  space_constraints.distribute_local_to_global(cell_matrix,
191  cell_rhs,
192  local_dof_indices,
193  system_matrix,
194  system_rhs);
195  }
196  }
197 
198 
199  deallog << "Assemble Nitsche contributions" << '\n';
200  {
201  TimerOutput::Scope timer_section(timer, "Assemble Nitsche terms");
202 
203  // Add the Nitsche's contribution to the system matrix. The coefficient
204  // that multiplies the inner product is equal to 2.0, and the penalty is
205  // set to 100.0.
206  NonMatching::
207  assemble_nitsche_with_exact_intersections<spacedim, dim, spacedim>(
208  space_dh,
209  cells_and_quads,
210  system_matrix,
211  space_constraints,
212  ComponentMask(),
214  nitsche_coefficient,
215  penalty);
216 
217  // Add the Nitsche's contribution to the rhs. The embedded value is
218  // parsed from the parameter file, while we have again the constant 2.0
219  // in front of that term, parsed as above from command line. Finally, we
220  // have the penalty parameter as before.
221  NonMatching::
222  create_nitsche_rhs_with_exact_intersections<spacedim, dim, spacedim>(
223  space_dh,
224  cells_and_quads,
225  system_rhs,
226  space_constraints,
228  embedded_value,
230  penalty);
231  }
232  }
233 
234 
235  // We solve the resulting system as done in the classical Poisson example.
236  template <int dim, int spacedim>
237  void
239  {
240  TimerOutput::Scope timer_section(timer, "Solve system");
241  deallog << "Solve system" << std::endl;
242  preconditioner.initialize(system_matrix);
243  const auto A = linear_operator<Vector<double>>(system_matrix);
244  const auto Ainv = inverse_operator(A, preconditioner);
245  solution = Ainv * system_rhs;
246  space_constraints.distribute(solution);
247  }
248 
249 
250 
251  // Finally, we output the solution living in the embedding space, just
252  // like all the other programs.
253  template <int dim, int spacedim>
254  void
256  const unsigned cycle) const
257  {
258  TimerOutput::Scope timer_section(timer, "Output results");
259  deallog << "Output results" << std::endl;
260  // Save each cycle in its own file
261  const auto suffix =
264  grid_refinement.get_n_refinement_cycles()));
265  data_out.attach_dof_handler(space_dh, suffix);
266  data_out.add_data_vector(solution, component_names);
267  data_out.write_data_and_clear(*mapping);
268  // Save the grids
269  {
270  std::ofstream output_test_space("space_grid.vtk");
271  GridOut().write_vtk(space_triangulation, output_test_space);
272  std::ofstream output_test_embedded("embedded_grid.vtk");
273  GridOut().write_vtk(embedded_triangulation, output_test_embedded);
274  }
275  }
276 
277 
278  // The run() method here differs only in the call to
279  // NonMatching::compute_intersection().
280  template <int dim, int spacedim>
281  void
283  {
284  deallog.pop();
285  deallog.depth_console(console_level);
286 
287  generate_grids();
288  for (const auto &cycle : grid_refinement.get_refinement_cycles())
289  {
290  deallog.push("Cycle " + Utilities::int_to_string(cycle));
291 
292 
293 
294  // Here we compute all the things we need to assemble the Nitsche's
295  // contributions, namely the two cached triangulations and a degree to
296  // integrate over the intersections.
297  cells_and_quads = NonMatching::compute_intersection(
298  *space_cache, *embedded_cache, 2 * space_fe().tensor_degree() + 1);
299  setup_system();
300  assemble_system();
301  solve();
302 
303 
304  error_table.error_from_exact(space_dh, solution, exact_solution);
305  output_results(cycle);
306 
307 
308  if (cycle < grid_refinement.get_n_refinement_cycles() - 1)
309  grid_refinement.estimate_mark_refine(*mapping,
310  space_dh,
311  solution,
312  space_triangulation);
313 
314 
315  deallog.pop();
316  }
317  // Make sure we output the error table after the last cycle
318  error_table.output_table(std::cout);
319  }
320 
321  // We explicitly instantiate all of the different combinations of dim and
322  // spacedim, so that users can run this in different dimension in the tests
323  template class PoissonNitscheInterface<1, 2>;
324  template class PoissonNitscheInterface<2, 2>;
325  template class PoissonNitscheInterface<2, 3>;
326  template class PoissonNitscheInterface<3, 3>;
327 
328  } // namespace Serial
329 } // namespace PDEs
void write_vtk(const Triangulation< dim, spacedim > &tria, std::ostream &out) const
std::ostream & get_console()
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
Imposing an interaface condition in Poisson problem, serial version.
void output_results(const unsigned cycle) const
void setup_system()
Setup dofs, constraints, and matrices.
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.