phaseflow
FEM solver for the Navier-Stokes-Boussinesq equations coupled with enthalpy-based phase change
 All Classes Namespaces Files Functions Variables Macros Pages
pf_system.h
Go to the documentation of this file.
1 #ifndef _pf_system_h_
2 #define _pf_system_h_
3 
5 template<int dim>
6 void Phaseflow<dim>::setup_system()
7 {
8 
9  this->dof_handler.distribute_dofs(this->fe);
10 
11  DoFRenumbering::component_wise(this->dof_handler);
12 
13  std::cout << std::endl
14  << "==========================================="
15  << std::endl
16  << "Number of active cells: " << this->triangulation.n_active_cells()
17  << std::endl
18  << "Number of degrees of freedom: " << this->dof_handler.n_dofs()
19  << std::endl
20  << std::endl;
21 
22  this->constraints.clear();
23 
24  DoFTools::make_hanging_node_constraints(
25  this->dof_handler,
26  this->constraints);
27 
28  this->constraints.close();
29 
30  DynamicSparsityPattern dsp(this->dof_handler.n_dofs());
31 
32  DoFTools::make_sparsity_pattern(
33  this->dof_handler,
34  dsp,
35  this->constraints,
36  /*keep_constrained_dofs = */ true);
37 
38  this->sparsity_pattern.copy_from(dsp);
39 
40  this->system_matrix.reinit(this->sparsity_pattern);
41 
42  this->solution.reinit(this->dof_handler.n_dofs());
43 
44  this->newton_residual.reinit(this->dof_handler.n_dofs());
45 
46  this->newton_solution.reinit(this->dof_handler.n_dofs());
47 
48  this->old_solution.reinit(this->dof_handler.n_dofs());
49 
50  this->old_newton_solution.reinit(this->dof_handler.n_dofs());
51 
52  this->system_rhs.reinit(this->dof_handler.n_dofs());
53 
54 }
55 
90 template<int dim>
91 void Phaseflow<dim>::assemble_system()
92 {
93  this->system_matrix = 0.;
94 
95  this->system_rhs = 0.;
96 
100  const double PENALTY = 1.e-7; // @todo: Expose this to ParameterHandler.
101 
102  const double
103  Ra = RAYLEIGH_NUMBER,
104  Pr = PRANDTL_NUMBER,
105  Re = REYNOLDS_NUMBER;
106 
107  const double K = SOLID_CONDUCTIVITY/LIQUID_CONDUCTIVITY;
108 
109  Tensor<1, dim> g; // @todo: Make this const.
110  for (unsigned int i = 0; i < dim; ++i)
111  {
112  g[i] = this->params.physics.gravity[i];
113  }
114 
115  const double mu_l = this->params.physics.liquid_dynamic_viscosity;
116 
120  auto f_B = [Ra, Pr, Re, g](const double _theta)
121  {
122  return _theta*Ra/(Pr*Re*Re)*g;
123  };
124 
128  const Tensor<1, dim> df_B_over_dtheta(Ra/(Pr*Re*Re)*g);
129 
133  auto a = [](
134  const double _mu,
135  const Tensor<2, dim> _gradu,
136  const Tensor<2, dim> _gradv)
137  {
138  auto D = [](
139  const Tensor<2, dim> _gradw)
140  {
141  return 0.5*(_gradw + transpose(_gradw));
142  };
143 
144  return 2.*_mu*scalar_product(D(_gradu), D(_gradv));
145  };
146 
147  auto b = [](
148  const double _divu,
149  const double _q)
150  {
151  return -_divu*_q;
152  };
153 
154  auto c = [](
155  const Tensor<1, dim> _w,
156  const Tensor<2, dim> _gradz,
157  const Tensor<1, dim> _v)
158  {
159  return (_v*_gradz)*_w;
160  };
161 
166  QGauss<dim> quadrature_formula(SCALAR_DEGREE + 2);
167 
168  FEValues<dim> fe_values(
169  this->fe,
170  quadrature_formula,
171  update_values | update_gradients | update_quadrature_points | update_JxW_values);
172 
173  const unsigned int dofs_per_cell = this->fe.dofs_per_cell;
174 
175  const unsigned int n_quad_points = quadrature_formula.size();
176 
177  FullMatrix<double> local_matrix(dofs_per_cell, dofs_per_cell);
178 
179  Vector<double> local_rhs(dofs_per_cell);
180 
181  std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
182 
183  std::vector<Tensor<1,dim>> old_velocity_values(n_quad_points);
184 
185  std::vector<double> old_pressure_values(n_quad_points);
186 
187  std::vector<double> old_temperature_values(n_quad_points);
188 
189  std::vector<Tensor<1,dim>> old_newton_velocity_values(n_quad_points);
190 
191  std::vector<double> old_newton_pressure_values(n_quad_points);
192 
193  std::vector<double> old_newton_temperature_values(n_quad_points);
194 
195  std::vector<Tensor<2,dim>> old_newton_velocity_gradients(n_quad_points);
196 
197  std::vector<Tensor<1,dim>> old_newton_temperature_gradients(n_quad_points);
198 
199  std::vector<double> old_newton_velocity_divergences(n_quad_points);
200 
201  std::vector<Vector<double>> source_values(n_quad_points, Vector<double>(dim + 2));
202 
203  this->source_function.set_time(this->new_time);
204 
205  typename DoFHandler<dim>::active_cell_iterator
206  cell = this->dof_handler.begin_active(),
207  endc = this->dof_handler.end();
208 
209 
213  const double deltat = this->time_step_size;
214 
215  const double gamma = PENALTY;
216 
217  for (; cell != endc; ++cell)
218  {
219  fe_values.reinit(cell);
220 
221  fe_values[this->velocity_extractor].get_function_values(
222  this->old_solution,
223  old_velocity_values);
224 
225  fe_values[this->pressure_extractor].get_function_values(
226  this->old_solution,
227  old_pressure_values);
228 
229  fe_values[this->temperature_extractor].get_function_values(
230  this->old_solution,
231  old_temperature_values);
232 
233  fe_values[this->velocity_extractor].get_function_values(
234  this->old_newton_solution,
235  old_newton_velocity_values);
236 
237  fe_values[this->pressure_extractor].get_function_values(
238  this->old_newton_solution,
239  old_newton_pressure_values);
240 
241  fe_values[this->temperature_extractor].get_function_values(
242  this->old_newton_solution,
243  old_newton_temperature_values);
244 
245  fe_values[this->temperature_extractor].get_function_gradients(
246  this->old_newton_solution,
247  old_newton_temperature_gradients);
248 
249  fe_values[this->velocity_extractor].get_function_gradients(
250  this->old_newton_solution,
251  old_newton_velocity_gradients);
252 
253  fe_values[this->velocity_extractor].get_function_divergences(
254  this->old_newton_solution,
255  old_newton_velocity_divergences);
256 
257  std::vector<Tensor<1, dim>> velocity_fe_values(dofs_per_cell);
258  std::vector<double> pressure_fe_values(dofs_per_cell);
259  std::vector<double> temperature_fe_values(dofs_per_cell);
260  std::vector<Tensor<1, dim>> grad_temperature_fe_values(dofs_per_cell);
261  std::vector<Tensor<2, dim>> grad_velocity_fe_values(dofs_per_cell);
262  std::vector<double> div_velocity_fe_values(dofs_per_cell);
263 
264  local_matrix = 0.;
265 
266  local_rhs = 0.;
267 
268  this->source_function.vector_value_list(
269  fe_values.get_quadrature_points(),
270  source_values);
271 
272  for (unsigned int quad = 0; quad< n_quad_points; ++quad)
273  {
274  /* Name local variables to match notation in Danaila 2014 */
275  const Tensor<1, dim> u_n = old_velocity_values[quad];
276  const double theta_n = old_temperature_values[quad];
277 
278  const Tensor<1, dim> u_k = old_newton_velocity_values[quad];
279  const double p_k = old_newton_pressure_values[quad];
280  const double theta_k = old_newton_temperature_values[quad];
281 
282  const Tensor<1, dim> gradtheta_k = old_newton_temperature_gradients[quad];
283  const Tensor<2, dim> gradu_k = old_newton_velocity_gradients[quad];
284  const double divu_k = old_newton_velocity_divergences[quad];
285 
286  Tensor<1, dim> s_u;
287 
288  for (unsigned int d = 0; d < dim; ++d)
289  {
290  s_u[d] = source_values[quad][d];
291  }
292 
293  const double s_p = source_values[quad][dim];
294 
295  const double s_theta = source_values[quad][dim+1];
296 
297  for (unsigned int dof = 0; dof < dofs_per_cell; ++dof)
298  {
299  velocity_fe_values[dof] = fe_values[this->velocity_extractor].value(dof, quad);
300  pressure_fe_values[dof] = fe_values[this->pressure_extractor].value(dof, quad);
301  temperature_fe_values[dof] = fe_values[this->temperature_extractor].value(dof, quad);
302  grad_temperature_fe_values[dof] = fe_values[this->temperature_extractor].gradient(dof, quad);
303  grad_velocity_fe_values[dof] = fe_values[this->velocity_extractor].gradient(dof, quad);
304  div_velocity_fe_values[dof] = fe_values[this->velocity_extractor].divergence(dof, quad);
305  }
306 
307  for (unsigned int i = 0; i < dofs_per_cell; ++i)
308  {
309  /* Name local variables to match notation in Danaila 2014 */
310  const Tensor<1, dim> v = velocity_fe_values[i];
311  const double q = pressure_fe_values[i];
312  const double phi = temperature_fe_values[i];
313  const Tensor<1, dim> gradphi = grad_temperature_fe_values[i];
314  const Tensor<2, dim> gradv = grad_velocity_fe_values[i];
315  const double divv = div_velocity_fe_values[i];
316 
317  /* @todo Here I implemented the form derived in danaila2014newton, where they
318  multiplied by test functions from the right. deal.II's tutorials recommend to get
319  in the habit of instead multiplying from the left, to avoid a common class of errors.
320  If verification fails, then I should try deriving my own form, with the left multiplication, and see if this helps.
321  */
322  for (unsigned int j = 0; j< dofs_per_cell; ++j)
323  {
324  const Tensor<1, dim> u_w = velocity_fe_values[j];
325  const double p_w = pressure_fe_values[j];
326  const double theta_w = temperature_fe_values[j];
327  const Tensor<1, dim> gradtheta_w = grad_temperature_fe_values[j];
328  const Tensor<2, dim> gradu_w = grad_velocity_fe_values[j];
329  const double divu_w = div_velocity_fe_values[j];
330 
331  local_matrix(i,j) += (
332  b(divu_w, q) - gamma*p_w*q // Mass
333  + scalar_product(u_w, v)/deltat + c(u_w, gradu_k, v) + c(u_k, gradu_w, v) + a(mu_l, gradu_w, gradv) + b(divv, p_w) // Momentum: Incompressible Navier-Stokes
334  + scalar_product(theta_w*df_B_over_dtheta, v) // Momentum: Bouyancy (Classical linear Boussinesq approximation)
335  + theta_w*phi/deltat - scalar_product(u_k, gradphi)*theta_w - scalar_product(u_w, gradphi)*theta_k + scalar_product(K/Pr*gradtheta_w, gradphi) // Energy
336  )*fe_values.JxW(quad); /* Map to the reference element */
337 
338  }
339 
340  local_rhs(i) += (
341  b(divu_k, q) - gamma*p_k*q // Mass
342  + scalar_product(u_k - u_n, v)/deltat + c(u_k, gradu_k, v) + a(mu_l, gradu_k, gradv) + b(divv, p_k) // Momentum: Incompressible Navier-Stokes
343  + scalar_product(f_B(theta_k), v) // Momentum: Bouyancy (Classical linear Boussinesq approximation)
344  + (theta_k - theta_n)*phi/deltat - scalar_product(u_k, gradphi)*theta_k + scalar_product(K/Pr*gradtheta_k, gradphi) // Energy
345  + s_p*q + scalar_product(s_u, v) + s_theta*phi // Source (MMS)
346  )*fe_values.JxW(quad);
347 
348  }
349 
350  }
351 
352  // Export local contributions to the global system
353  cell->get_dof_indices(local_dof_indices);
354 
355  this->constraints.distribute_local_to_global(
356  local_matrix, local_rhs, local_dof_indices,
357  this->system_matrix, this->system_rhs);
358 
359  }
360 
361 }
362 
363 template<int dim>
364 void Phaseflow<dim>::interpolate_boundary_values(
365  Function<dim>* function,
366  std::map<types::global_dof_index, double> &boundary_values) const
367 {
368  for (unsigned int ib = 0; ib < this->params.boundary_conditions.strong_boundaries.size(); ++ib) /* For each boundary */
369  {
370  unsigned int b = this->params.boundary_conditions.strong_boundaries[ib];
371 
372  auto mask = this->params.boundary_conditions.strong_masks[b];
373 
374  for (auto field_name : FIELD_NAMES) /* For each field variable */
375  {
376  if (std::find(mask.begin(), mask.end(), field_name) == mask.end()) /* Skip if the field name is not in the mask */
377  {
378  continue;
379  }
380 
381  /* @todo
382  Is there some way to contain the extractors (or pointers to them) in a single object that can be indexed?
383  Neither std::vector<void*> nor tuple (because the tuple could not be indexed with a variable) worked, and I'm out of ideas. */
384  if (field_name == "velocity")
385  {
386  VectorTools::interpolate_boundary_values(
387  this->dof_handler, b, *function, boundary_values,
388  this->fe.component_mask(this->velocity_extractor));
389  }
390  else if (field_name == "pressure")
391  {
392  VectorTools::interpolate_boundary_values(
393  this->dof_handler, b, *function, boundary_values,
394  this->fe.component_mask(this->pressure_extractor));
395  }
396  else if (field_name == "temperature")
397  {
398  VectorTools::interpolate_boundary_values(
399  this->dof_handler, b, *function, boundary_values,
400  this->fe.component_mask(this->temperature_extractor));
401  }
402  else
403  {
404  assert(false);
405  }
406 
407  }
408 
409  }
410 
411 }
412 
414 template<int dim>
415 void Phaseflow<dim>::apply_boundary_values_and_constraints()
416 {
417  /* Since we are applying boundary conditions to the Newton linearized system
418  to compute a residual, we want to apply the boundary conditions residual, rather
419  than the user supplied boundary conditions.
420 
421  To do this, we evaluate the BC's both at the new time and the current time,
422  and we apply the difference. */
423  std::map<types::global_dof_index, double> residual_boundary_values, boundary_values, new_boundary_values;
424 
425  /* @todo Using a FEFieldFunction to interpolate the solution values seems like a terrible idea,
426  since FEFieldFunction is designed to interpolate within the domain. Is there another method when I really just
427  need the solution values on boundary nodes?
428 
429  Another reason this is a terrible approach: I am essentially interpolating all of the finite element functions
430  to get velocity, pressure, and temperature values, and then these have to be decomposed onto the finite element functions again with interpolate_boundary_values. There should be an easy way to just use the map<global_dof_index, double> to do this directly. */
431  Functions::FEFieldFunction<dim> solution_field_function(this->dof_handler, this->solution);
432 
433  this->interpolate_boundary_values(&solution_field_function, boundary_values);
434 
435  this->boundary_function.set_time(this->new_time);
436 
437  this->interpolate_boundary_values(&this->boundary_function, new_boundary_values);
438 
439  for (auto m: new_boundary_values)
440  {
441  residual_boundary_values.insert(m);
442 
443  residual_boundary_values[m.first] -= boundary_values[m.first];
444  }
445 
446  MatrixTools::apply_boundary_values(
447  residual_boundary_values,
448  this->system_matrix,
449  this->newton_residual,
450  this->system_rhs);
451 }
452 
453 
459 template<int dim>
460 void Phaseflow<dim>::solve_linear_system()
461 {
463  {
464  Output::write_linear_system(this->system_matrix, this->system_rhs);
465  }
466 
467  SparseDirectUMFPACK A_inv;
468 
469  A_inv.initialize(this->system_matrix);
470 
471  A_inv.vmult(this->newton_residual, this->system_rhs);
472 
473  this->constraints.distribute(this->newton_residual);
474 
475  std::cout << "Solved linear system" << std::endl;
476 
477 }
478 
479 #endif
const double LIQUID_CONDUCTIVITY
Definition: pf_global_parameters.h:20
const unsigned int SCALAR_DEGREE
Definition: pf_global_parameters.h:22
const double REYNOLDS_NUMBER
Definition: pf_global_parameters.h:16
std::vector< std::string > FIELD_NAMES({"velocity","pressure","temperature"})
void write_linear_system(SparseMatrix< double > &A, Vector< double > &b)
Definition: output.h:53
const double SOLID_CONDUCTIVITY
Definition: pf_global_parameters.h:18
const bool WRITE_LINEAR_SYSTEM
Definition: pf_global_parameters.h:26
const double RAYLEIGH_NUMBER
Definition: pf_global_parameters.h:12
const double PRANDTL_NUMBER
Definition: pf_global_parameters.h:14