libMesh
coupled_system.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2024 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 
19 // Example includes
20 #include "coupled_system.h"
21 
22 // libMesh includes
23 #include "libmesh/dirichlet_boundaries.h"
24 #include "libmesh/dof_map.h"
25 #include "libmesh/fe_base.h"
26 #include "libmesh/fe_interface.h"
27 #include "libmesh/fem_context.h"
28 #include "libmesh/getpot.h"
29 #include "libmesh/mesh.h"
30 #include "libmesh/parallel.h"
31 #include "libmesh/quadrature.h"
32 #include "libmesh/string_to_enum.h"
33 #include "libmesh/zero_function.h"
34 
35 // C++ includes
36 #include <functional> // std::reference_wrapper
37 #include <memory>
38 
39 // Bring in everything from the libMesh namespace
40 using namespace libMesh;
41 
42 // Function to set the Dirichlet boundary function for the inlet boundary velocities
43 class BdyFunction : public FunctionBase<Number>
44 {
45 public:
46  BdyFunction (unsigned int u_var,
47  unsigned int v_var,
48  int sign) :
49  _u_var(u_var),
50  _v_var(v_var),
51  _sign(sign)
52  { this->_initialized = true; }
53 
54  virtual Number operator() (const Point &,
55  const Real = 0)
56  { libmesh_not_implemented(); }
57 
58  virtual void operator() (const Point & p,
59  const Real,
60  DenseVector<Number> & output)
61  {
62  output.resize(2);
63  output.zero();
64  const Real y=p(1);
65  // Set the parabolic inflow boundary conditions at stations 0 & 1
66  output(_u_var) = (_sign)*((y-2) * (y-3));
67  output(_v_var) = 0;
68  }
69 
70  virtual std::unique_ptr<FunctionBase<Number>> clone() const
71  { return std::make_unique<BdyFunction>(_u_var, _v_var, int(_sign)); }
72 
73 private:
74  const unsigned int _u_var, _v_var;
75  const Real _sign;
76 };
77 
78 
80 {
81  // Check the input file for Reynolds number, application type,
82  // approximation type
83  GetPot infile("coupled_system.in");
84  Peclet = infile("Peclet", 1.);
85  unsigned int pressure_p = infile("pressure_p", 1);
86  std::string fe_family = infile("fe_family", std::string("LAGRANGE"));
87 
88  // LBB needs better-than-quadratic velocities for better-than-linear
89  // pressures, and libMesh needs non-Lagrange elements for
90  // better-than-quadratic velocities.
91  libmesh_assert((pressure_p == 1) || (fe_family != "LAGRANGE"));
92 
93  FEFamily fefamily = Utility::string_to_enum<FEFamily>(fe_family);
94 
95  // Add the velocity components "u" & "v". They
96  // will be approximated using second-order approximation.
97  u_var = this->add_variable ("u", static_cast<Order>(pressure_p+1),
98  fefamily);
99  v_var = this->add_variable ("v", static_cast<Order>(pressure_p+1),
100  fefamily);
101 
102  // Add the pressure variable "p". This will
103  // be approximated with a first-order basis,
104  // providing an LBB-stable pressure-velocity pair.
105  p_var = this->add_variable ("p", static_cast<Order>(pressure_p),
106  fefamily);
107 
108  // Add the Concentration variable "C". They will
109  // be approximated using second-order approximation, the same as the velocity components
110  C_var = this->add_variable ("C", static_cast<Order>(pressure_p+1),
111  fefamily);
112 
113  // Tell the system to march velocity and concentration forward in
114  // time, with first order time derivatives, but leave pressure as a
115  // constraint only
116  this->time_evolving(u_var, 1);
117  this->time_evolving(v_var, 1);
118  this->time_evolving(C_var, 1);
119 
120  // Useful debugging options
121  this->verify_analytic_jacobians = infile("verify_analytic_jacobians", 0.);
122 
123  // Set Dirichlet boundary conditions
124  const boundary_id_type left_inlet_id = 0;
125 
126  const boundary_id_type right_inlet_id = 1;
127  std::set<boundary_id_type> right_inlet_bdy;
128  right_inlet_bdy.insert(right_inlet_id);
129 
130  const boundary_id_type outlets_id = 2;
131  std::set<boundary_id_type> outlets_bdy;
132  outlets_bdy.insert(outlets_id);
133 
134  const boundary_id_type wall_id = 3;
135 
136  // The zero and constant functions
138  ConstFunction<Number> one(1);
139 
140  // We need two boundary functions for the inlets, because the signs on the velocities
141  // will be different
142  int velocity_sign = 1;
143  BdyFunction inflow_left(u_var, v_var, -velocity_sign);
144  BdyFunction inflow_right(u_var, v_var, velocity_sign);
145 
146 #ifdef LIBMESH_ENABLE_DIRICHLET
147  // On the walls we will apply the no slip and no penetration boundary condition, u=0, v=0
148  this->get_dof_map().add_dirichlet_boundary
149  (DirichletBoundary ({wall_id}, {u_var,v_var}, &zero));
150 
151  // On the inlet (left), we apply parabolic inflow boundary conditions for the velocity, u = - (y-2)*(y-3), v=0
152  // and set C = 1
153  this->get_dof_map().add_dirichlet_boundary
154  (DirichletBoundary ({left_inlet_id}, {u_var,v_var}, &inflow_left));
155  this->get_dof_map().add_dirichlet_boundary
156  (DirichletBoundary ({left_inlet_id}, {C_var}, &one));
157 
158  // On the inlet (right), we apply parabolic inflow boundary conditions for the velocity, u = (y-2)*(y-3), v=0
159  // and set C = 0
160  this->get_dof_map().add_dirichlet_boundary
161  (DirichletBoundary (right_inlet_bdy, {u_var,v_var}, &inflow_right));
162  this->get_dof_map().add_dirichlet_boundary
163  (DirichletBoundary (right_inlet_bdy, {C_var}, &zero));
164 #endif // LIBMESH_ENABLE_DIRICHLET
165 
166  // Note that the remaining boundary conditions are the natural boundary conditions for the concentration
167  // on the wall (grad(c) dot n = 0) and natural boundary conditions for the velocity and the concentration
168  // on the outlets ((grad(velocity) dot n - p n) dot t = 0, grad(C) dot n = 0)
169 
170  // Do the parent's initialization after variables and boundary constraints are defined
172 }
173 
175 {
176  FEMContext & c = cast_ref<FEMContext &>(context);
177 
178  // We should prerequest all the data
179  // we will need to build the linear system.
180  // Note that the concentration and velocity components
181  // use the same basis.
182  FEBase * u_elem_fe = nullptr;
183  c.get_element_fe(u_var, u_elem_fe);
184  u_elem_fe->get_JxW();
185  u_elem_fe->get_phi();
186  u_elem_fe->get_dphi();
187  u_elem_fe->get_xyz();
188 
189  FEBase * p_elem_fe = nullptr;
190  c.get_element_fe(p_var, p_elem_fe);
191  p_elem_fe->get_phi();
192 
193  FEBase * side_fe = nullptr;
194  c.get_side_fe(u_var, side_fe);
195 
196  side_fe->get_JxW();
197  side_fe->get_phi();
198  side_fe->get_xyz();
199 
200  // Don't waste time on side computations for p
201  FEBase * p_side_fe = nullptr;
202  c.get_side_fe(p_var, p_side_fe);
203  p_side_fe->get_nothing();
204 }
205 
206 
207 bool CoupledSystem::element_time_derivative (bool request_jacobian,
208  DiffContext & context)
209 {
210  FEMContext & c = cast_ref<FEMContext &>(context);
211 
212  // First we get some references to cell-specific data that
213  // will be used to assemble the linear system.
214  FEBase * u_elem_fe = nullptr;
215  c.get_element_fe(u_var, u_elem_fe);
216 
217  // Element Jacobian * quadrature weights for interior integration
218  const std::vector<Real> & JxW = u_elem_fe->get_JxW();
219 
220  // The velocity shape functions at interior quadrature points.
221  const std::vector<std::vector<Real>> & phi = u_elem_fe->get_phi();
222 
223  // The velocity shape function gradients at interior
224  // quadrature points.
225  const std::vector<std::vector<RealGradient>> & dphi = u_elem_fe->get_dphi();
226 
227  // The pressure shape functions at interior
228  // quadrature points.
229  FEBase * p_elem_fe = nullptr;
230  c.get_element_fe(p_var, p_elem_fe);
231 
232  const std::vector<std::vector<Real>> & psi = p_elem_fe->get_phi();
233 
234  // The number of local degrees of freedom in each variable
235  const unsigned int n_p_dofs = c.n_dof_indices(p_var);
236  const unsigned int n_u_dofs = c.n_dof_indices(u_var);
237  libmesh_assert_equal_to (n_u_dofs, c.n_dof_indices(v_var));
238 
239  // Stokes equation stiffness matrix diagonal blocks. There is no
240  // uv/vu coupling for linear Stokes.
241  std::reference_wrapper<DenseSubMatrix<Number>> Kdiag[2] =
242  {
243  c.get_elem_jacobian(u_var, u_var),
244  c.get_elem_jacobian(v_var, v_var)
245  };
246 
247  // Stokes equation velocity-pressure coupling blocks
248  // Kup
249  // Kvp
250  std::reference_wrapper<DenseSubMatrix<Number>> B[2] =
251  {
252  c.get_elem_jacobian(u_var, p_var),
253  c.get_elem_jacobian(v_var, p_var),
254  };
255 
256  // Stokes equation residuals
257  // Fu
258  // Fv
259  std::reference_wrapper<DenseSubVector<Number>> F[2] =
260  {
261  c.get_elem_residual(u_var),
262  c.get_elem_residual(v_var)
263  };
264 
265  // Concentration/velocity coupling blocks
266  // KCu
267  // KCv
268  std::reference_wrapper<DenseSubMatrix<Number>> C[2] =
269  {
270  c.get_elem_jacobian(C_var, u_var),
271  c.get_elem_jacobian(C_var, v_var)
272  };
273 
274  // Concentration diagonal block and residual
275  DenseSubMatrix<Number> & KCC = c.get_elem_jacobian(C_var, C_var);
277 
278  // Now we will build the element Jacobian and residual.
279  // Constructing the residual requires the solution and its
280  // gradient from the previous timestep. This must be
281  // calculated at each quadrature point by summing the
282  // solution degree-of-freedom values by the appropriate
283  // weight functions.
284  unsigned int n_qpoints = c.get_element_qrule().n_points();
285 
286  // Store (grad_u, grad_v) at current Newton iterate
287  Gradient grad_uv[2];
288 
289  // Velocity at current Newton iterate
291 
292  for (unsigned int qp=0; qp != n_qpoints; qp++)
293  {
294  // Compute the solution & its gradient at the current Newton iterate
295  Number p = c.interior_value(p_var, qp);
296  Gradient grad_C = c.interior_gradient(C_var, qp);
297 
298  c.interior_value(u_var, qp, U(0));
299  c.interior_value(v_var, qp, U(1));
300  c.interior_gradient(u_var, qp, grad_uv[0]);
301  c.interior_gradient(v_var, qp, grad_uv[1]);
302 
303  // First, an i-loop over the velocity degrees of freedom.
304  // We know that n_u_dofs == n_v_dofs so we can compute contributions
305  // for both at the same time.
306  for (unsigned int i=0; i != n_u_dofs; i++)
307  {
308  // Stokes equations residuals
309  for (unsigned int d=0; d<2; ++d)
310  F[d](i) += JxW[qp] *
311  (p*dphi[i][qp](d) - // pressure term
312  (grad_uv[d]*dphi[i][qp])); // diffusion term
313 
314  // Concentration Equation Residual
315  FC(i) += JxW[qp] *
316  ((U*grad_C)*phi[i][qp] + // convection term
317  (1./Peclet)*(grad_C*dphi[i][qp])); // diffusion term
318 
319  // Note that the Fp block is identically zero unless we are using
320  // some kind of artificial compressibility scheme...
321 
322  if (request_jacobian && c.elem_solution_derivative)
323  {
325 
326  for (unsigned int j=0; j != n_u_dofs; j++)
327  {
328  for (unsigned int d=0; d<2; ++d)
329  {
330  // Matrix contributions for the uu and vv couplings.
331  Kdiag[d](i,j) += JxW[qp] * (-(dphi[i][qp]*dphi[j][qp]));
332 
333  // Matrix contributions for the Cu and Cv couplings.
334  C[d](i,j) += JxW[qp] * ((phi[j][qp]*grad_C(d))*phi[i][qp]);
335  }
336 
337  // Concentration equation Jacobian contribution
338  KCC(i,j) += JxW[qp]*
339  ((U*dphi[j][qp])*phi[i][qp] + // nonlinear term (convection)
340  (1./Peclet)*(dphi[j][qp]*dphi[i][qp])); // diffusion term
341  }
342 
343  // Matrix contributions for the up and vp couplings.
344  for (unsigned int j=0; j != n_p_dofs; j++)
345  for (unsigned int d=0; d<2; ++d)
346  B[d](i,j) += JxW[qp]*psi[j][qp]*dphi[i][qp](d);
347  }
348  }
349  } // end of the quadrature point qp-loop
350 
351  return request_jacobian;
352 }
353 
354 
355 bool CoupledSystem::element_constraint (bool request_jacobian,
356  DiffContext & context)
357 {
358  FEMContext & c = cast_ref<FEMContext &>(context);
359 
360  // Here we define some references to cell-specific data that
361  // will be used to assemble the linear system.
362  FEBase * u_elem_fe = nullptr;
363  c.get_element_fe(u_var, u_elem_fe);
364 
365  FEBase * p_elem_fe = nullptr;
366  c.get_element_fe(p_var, p_elem_fe);
367 
368  // Element Jacobian * quadrature weight for interior integration
369  const std::vector<Real> & JxW = u_elem_fe->get_JxW();
370 
371  // The velocity shape function gradients at interior
372  // quadrature points.
373  const std::vector<std::vector<RealGradient>> & dphi = u_elem_fe->get_dphi();
374 
375  // The pressure shape functions at interior
376  // quadrature points.
377  const std::vector<std::vector<Real>> & psi = p_elem_fe->get_phi();
378 
379  // The number of local degrees of freedom in each variable
380  const unsigned int n_u_dofs = c.n_dof_indices(u_var);
381  const unsigned int n_p_dofs = c.n_dof_indices(p_var);
382 
383  // pressure-velocity coupling blocks
384  // Kpu Kpv
385  std::reference_wrapper<DenseSubMatrix<Number>> B[2] =
386  {
387  c.get_elem_jacobian(p_var, u_var),
388  c.get_elem_jacobian(p_var, v_var)
389  };
390 
391  // The subvectors and submatrices we need to fill:
393 
394  // Add the constraint given by the continuity equation
395  unsigned int n_qpoints = c.get_element_qrule().n_points();
396 
397  // Store (grad_u, grad_v) at current Newton iterate
398  Gradient grad_uv[2];
399 
400  for (unsigned int qp=0; qp != n_qpoints; qp++)
401  {
402  // Compute the velocity gradient at the old Newton iterate
403  c.interior_gradient(u_var, qp, grad_uv[0]),
404  c.interior_gradient(v_var, qp, grad_uv[1]);
405 
406  // Now a loop over the pressure degrees of freedom. This
407  // computes the contributions of the continuity equation.
408  for (unsigned int i=0; i != n_p_dofs; i++)
409  {
410  for (unsigned int d=0; d<2; ++d)
411  Fp(i) += JxW[qp] * psi[i][qp] * grad_uv[d](d);
412 
413  if (request_jacobian && c.elem_solution_derivative)
414  {
416 
417  for (unsigned int j=0; j != n_u_dofs; j++)
418  for (unsigned int d=0; d<2; ++d)
419  B[d](i,j) += JxW[qp]*psi[i][qp]*dphi[j][qp](d);
420  }
421  }
422  } // end of the quadrature point qp-loop
423 
424  return request_jacobian;
425 }
426 
428 {
429  // We need to overload the postprocess function to set the
430  // computed_QoI variable of the CoupledSystem class to the qoi value
431  // stored in System::qoi[0]
432  computed_QoI = this->get_qoi_value(0);
433 }
434 
435 // These functions supply the nonlinear weighting for the adjoint residual error estimate which
436 // arise due to the convection term in the convection-diffusion equation:
437 // ||e((u_1)_h C,1)||_{L2} ||e(C^*)||_{L2} + ||e(u1 C,1_h)||_{L2} ||e(C^*)||_{L2}
438 // ||e((u_2)_h C,2)||_{L2} ||e(C^*)||_{L2} + ||e(u2 C,2_h)||_{L2} ||e(C^*)||_{L2}
439 // These functions compute (u_1)_h or C,1_h , and (u_2)_h or C,2_h , and supply it to the weighted patch recovery error estimator
440 // In CoupledFEMFunctionsx, the object built with var = 0, returns the (u_1)_h weight, while
441 // the object built with var = 1, returns the C,1_h weight. The switch statement
442 // distinguishes the behavior of the two objects
443 // Same thing for CoupledFEMFunctionsy
445  const Point & p,
446  const Real /* time */)
447 {
448  Number weight = 0.0;
449 
450  switch(var)
451  {
452  case 0:
453  {
454  Gradient grad_C = c.point_gradient(3, p);
455 
456  weight = grad_C(0);
457  }
458  break;
459 
460  case 3:
461  {
462  Number u = c.point_value(0, p);
463 
464  weight = u;
465  }
466  break;
467 
468  default:
469  libmesh_error_msg("Wrong variable number " \
470  << var \
471  << " passed to CoupledFEMFunctionsx object! Quitting!");
472  }
473 
474  return weight;
475 }
476 
478  const Point & p,
479  const Real /* time */)
480 {
481  Number weight = 0.0;
482 
483  switch(var)
484  {
485  case 1:
486  {
487  Gradient grad_C = c.point_gradient(3, p);
488 
489  weight = grad_C(1);
490  }
491  break;
492 
493  case 3:
494  {
495  Number v = c.point_value(1, p);
496 
497  weight = v;
498  }
499  break;
500 
501  default:
502  libmesh_error_msg("Wrong variable number " \
503  << var \
504  << " passed to CoupledFEMFunctionsy object! Quitting!");
505  }
506 
507  return weight;
508 }
const DenseMatrix< Number > & get_elem_jacobian() const
Const accessor for element Jacobian.
Definition: diff_context.h:274
Number interior_value(unsigned int var, unsigned int qp) const
Definition: fem_context.C:407
virtual void init_data()
Initializes the member data fields associated with the system, so that, e.g., assemble() may be used...
This class provides all data required for a physics package (e.g.
Definition: diff_context.h:55
void get_side_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for edge/face (2D/3D) finite element object for variable var for the largest dimension in th...
Definition: fem_context.h:317
virtual void zero() override final
Set every element in the vector to 0.
Definition: dense_vector.h:398
ConstFunction that simply returns 0.
Definition: zero_function.h:38
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:374
virtual bool element_constraint(bool request_jacobian, DiffContext &context)
Adds the constraint contribution on elem to elem_residual.
unsigned int n_dof_indices() const
Total number of dof indices on the element.
Definition: diff_context.h:395
This class allows one to associate Dirichlet boundary values with a given set of mesh boundary ids an...
virtual void postprocess()
Runs a postprocessing loop over all elements, and if postprocess_sides is true over all sides...
const unsigned int _v_var
This class defines a vector in LIBMESH_DIM dimensional Real or Complex space.
The libMesh namespace provides an interface to certain functionality in the library.
const Number zero
.
Definition: libmesh.h:280
Number point_value(unsigned int var, const Point &p) const
Definition: fem_context.C:869
Defines a dense subvector for use in finite element computations.
BdyFunction(unsigned int u_var, unsigned int v_var, int sign)
Definition: assembly.h:38
const Real _sign
Gradient interior_gradient(unsigned int var, unsigned int qp) const
Definition: fem_context.C:462
int8_t boundary_id_type
Definition: id_types.h:51
virtual std::unique_ptr< FunctionBase< Number > > clone() const
dof_id_type weight(const MeshBase &mesh, const processor_id_type pid)
Definition: mesh_tools.C:436
virtual void init_context(DiffContext &context)
Defines a dense submatrix for use in Finite Element-type computations.
Real elem_solution_derivative
The derivative of elem_solution with respect to the current nonlinear solution.
Definition: diff_context.h:496
virtual void init_data() override
Initializes the member data fields associated with the system, so that, e.g., assemble() may be used...
Definition: fem_system.C:873
libmesh_assert(ctx)
virtual_for_inffe const std::vector< Real > & get_JxW() const
Definition: fe_abstract.h:295
This class provides all data required for a physics package (e.g.
Definition: fem_context.h:62
unsigned int n_points() const
Definition: quadrature.h:123
virtual_for_inffe const std::vector< Point > & get_xyz() const
Definition: fe_abstract.h:272
virtual void operator()(const FEMContext &, const Point &, const Real, DenseVector< Number > &)
void get_nothing() const
Definition: fe_abstract.h:261
const DenseVector< Number > & get_elem_residual() const
Const accessor for element residual.
Definition: diff_context.h:242
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual bool element_time_derivative(bool request_jacobian, DiffContext &context)
Adds the time derivative contribution on elem to elem_residual.
Function that returns a single value that never changes.
void get_element_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for interior finite element object for variable var for the largest dimension in the mesh...
Definition: fem_context.h:277
FEFamily
defines an enum for finite element families.
Gradient point_gradient(unsigned int var, const Point &p) const
Definition: fem_context.C:915
Base class for functors that can be evaluated at a point and (optionally) time.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
void ErrorVector unsigned int
Definition: adjoints_ex3.C:360
This class forms the foundation from which generic finite elements may be derived.
const QBase & get_element_qrule() const
Accessor for element interior quadrature rule for the dimension of the current _elem.
Definition: fem_context.h:802
const std::vector< std::vector< OutputShape > > & get_phi() const
Definition: fe_base.h:207
virtual void operator()(const FEMContext &, const Point &, const Real, DenseVector< Number > &)