https://mooseframework.inl.gov
INSFVVelocityVariable.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #include "INSFVVelocityVariable.h"
11 #include "FaceInfo.h"
12 #include "ADReal.h"
13 #include "MathFVUtils.h"
14 #include "FVUtils.h"
15 #include "MooseError.h"
16 #include "SubProblem.h"
17 #include "INSFVNoSlipWallBC.h"
18 #include "INSFVAttributes.h"
19 #include "SystemBase.h"
20 #include "FVDirichletBCBase.h"
21 #include "Assembly.h"
22 
23 #include "libmesh/elem.h"
24 #include "libmesh/vector_value.h"
25 #include "libmesh/tensor_value.h"
26 #include "libmesh/dense_vector.h"
27 #include "libmesh/dense_matrix.h"
28 #include "libmesh/libmesh_common.h"
29 
30 #include <vector>
31 #include <utility>
32 
33 namespace Moose
34 {
35 namespace FV
36 {
37 template <typename ActionFunctor>
38 void
39 loopOverElemFaceInfo(const Elem & elem,
40  const MooseMesh & mesh,
41  const SubProblem & subproblem,
42  ActionFunctor & act)
43 {
44  const auto coord_type = subproblem.getCoordSystem(elem.subdomain_id());
46  mesh,
47  act,
48  coord_type,
49  coord_type == Moose::COORD_RZ ? subproblem.getAxisymmetricRadialCoord()
51 }
52 }
53 }
54 
55 registerMooseObject("NavierStokesApp", INSFVVelocityVariable);
56 
59 {
61 }
62 
64 {
65 }
66 
67 ADReal
69  const bool two_term_expansion,
70  const bool correct_skewness,
71  const Elem * elem_to_extrapolate_from,
72  const StateArg & time) const
73 {
74  ADReal boundary_value;
75  bool elem_to_extrapolate_from_is_fi_elem;
76  std::tie(elem_to_extrapolate_from, elem_to_extrapolate_from_is_fi_elem) =
77  [this, &fi, elem_to_extrapolate_from]() -> std::pair<const Elem *, bool>
78  {
79  if (elem_to_extrapolate_from)
80  return {elem_to_extrapolate_from, elem_to_extrapolate_from == &fi.elem()};
81  else
82  {
83  const auto [elem_guaranteed_to_have_dofs,
84  other_elem,
85  elem_guaranteed_to_have_dofs_is_fi_elem] =
87  libmesh_ignore(other_elem);
88  return {elem_guaranteed_to_have_dofs, elem_guaranteed_to_have_dofs_is_fi_elem};
89  }
90  }();
91 
92  if (two_term_expansion && isFullyDevelopedFlowFace(fi))
93  {
94  const Point vector_to_face = elem_to_extrapolate_from_is_fi_elem
95  ? (fi.faceCentroid() - fi.elemCentroid())
96  : (fi.faceCentroid() - fi.neighborCentroid());
97  boundary_value = uncorrectedAdGradSln(fi, time, correct_skewness) * vector_to_face +
98  getElemValue(elem_to_extrapolate_from, time);
99  }
100  else
102  fi, two_term_expansion, correct_skewness, elem_to_extrapolate_from, time);
103 
104  return boundary_value;
105 }
106 
107 VectorValue<ADReal>
109  const StateArg & time,
110  const bool correct_skewness) const
111 {
112  VectorValue<ADReal> unc_grad;
114  {
115  const auto & cell_gradient = adGradSln(&fi.elem(), time, correct_skewness);
116  const auto & normal = fi.normal();
117  unc_grad = cell_gradient - (cell_gradient * normal) * normal;
118  }
119  else
120  unc_grad = INSFVVariable::uncorrectedAdGradSln(fi, time, correct_skewness);
121 
122  return unc_grad;
123 }
124 
125 const VectorValue<ADReal> &
126 INSFVVelocityVariable::adGradSln(const Elem * const elem,
127  const StateArg & time,
128  bool correct_skewness) const
129 {
130  VectorValue<ADReal> * value_pointer = &_temp_cell_gradient;
131 
132  // We ensure that no caching takes place when we compute skewness-corrected
133  // quantities.
134  if (_cache_cell_gradients && !correct_skewness && time.state == 0)
135  {
136  auto it = _elem_to_grad.find(elem);
137 
138  if (it != _elem_to_grad.end())
139  return it->second;
140  }
141 
142  ADReal elem_value = getElemValue(elem, time);
143 
144  // We'll save off the extrapolated boundary faces (ebf) for later assignment to the cache (these
145  // are the keys). The boolean in the pair will denote whether the ebf face is a fully developed
146  // flow (e.g. fdf) face
147  std::vector<std::pair<const FaceInfo *, bool>> ebf_faces;
148 
149  try
150  {
151  VectorValue<ADReal> & grad = *value_pointer;
152 
153  bool volume_set = false;
154  Real volume = 0;
155 
156  // If we are performing a two term Taylor expansion for extrapolated boundary faces (faces on
157  // boundaries that do not have associated Dirichlet conditions), then the element gradient
158  // depends on the boundary face value and the boundary face value depends on the element
159  // gradient, so we have a system of equations to solve. Here is the system:
160  //
161  // \nabla \phi_C - \frac{1}{V} \sum_{ebf} \phi_{ebf} \vec{S_f} =
162  // \frac{1}{V} \sum_{of} \phi_{of} \vec{S_f} eqn.
163  // 1
164  //
165  // \phi_{ebf} - \vec{d_{Cf}} \cdot \nabla \phi_C = \phi_C eqn.
166  // 2
167  //
168  // where $C$ refers to the cell centroid, $ebf$ refers to an extrapolated boundary face, $of$
169  // refers to "other faces", e.g. non-ebf faces, and $f$ is a general face. $d_{Cf}$ is the
170  // vector drawn from the element centroid to the face centroid, and $\vec{S_f}$ is the surface
171  // vector, e.g. the face area times the outward facing normal
172  //
173  // NOTE: On fully developed flow boundaries, we modify our equation set slightly. In equation
174  // 2,
175  // $\nabla \phi_C$ is replaced with $\nabla \phi_{ebf,fdf}$ where $fdf$ denotes fully
176  // developed flow. Moreover, we introduce a third equation:
177  //
178  // \nabla \phi_{ebf,fdf} - \nabla \phi_C + (\nabla \phi_C \cdot \hat{n}) \hat{n} = 0 eqn.
179  // 3
180  //
181  // These modifications correspond to Moukalled's equations 15.140 and 15.141, but with
182  // $\hat{e_b}$ replaced with $\hat{n}$ because we believe the equation as written doesn't
183  // reflect the intent of the text, which is to guarantee a zero normal gradient in the
184  // direction of the surface normal
185 
186  // ebf eqns: element gradient coefficients, e.g. eqn. 2, LHS term 2 coefficient. *Note* that
187  // each element of the std::vector could correspond to a cell centroid gradient or to a face
188  // gradient computed on a fully developed flow face
189  std::vector<VectorValue<Real>> ebf_grad_coeffs;
190  // ebf eqns: rhs b values. These will actually correspond to the elem_value so we can use a
191  // pointer and avoid copying. This is the RHS of eqn. 2
192  std::vector<const ADReal *> ebf_b;
193 
194  // elem grad eqns: ebf coefficients, e.g. eqn. 1, LHS term 2 coefficients
195  std::vector<VectorValue<Real>> grad_ebf_coeffs;
196  // elem grad eqns: rhs b value, e.g. eqn. 1 RHS
197  VectorValue<ADReal> grad_b = 0;
198 
199  // eqn. 3 coefficients for cell centroid gradient, e.g. the coefficients that fall out of term
200  // 2 on the LHS of eqn. 3
201  std::vector<TensorValue<Real>> fdf_grad_centroid_coeffs;
202 
203  const unsigned int lm_dim = LIBMESH_DIM;
204 
205  auto action_functor = [&volume_set,
206  &volume,
207  &elem_value,
208 #ifndef NDEBUG
209  &elem,
210 #endif
211  &ebf_faces,
212  &ebf_grad_coeffs,
213  &ebf_b,
214  &grad_ebf_coeffs,
215  &grad_b,
216  &fdf_grad_centroid_coeffs,
217  correct_skewness,
218  &time,
219  this](const Elem & functor_elem,
220  const Elem * const neighbor,
221  const FaceInfo * const fi,
222  const Point & surface_vector,
223  Real coord,
224  const bool elem_has_info)
225  {
226  mooseAssert(fi, "We need a FaceInfo for this action_functor");
227  mooseAssert(elem == &functor_elem,
228  "Just a sanity check that the element being passed in is the one we passed out.");
229 
230  if (isExtrapolatedBoundaryFace(*fi, &functor_elem, time))
231  {
233  {
234  const bool fdf_face = isFullyDevelopedFlowFace(*fi);
235  ebf_faces.push_back(std::make_pair(fi, fdf_face));
236 
237  // eqn. 2
238  ebf_grad_coeffs.push_back(-1. * (elem_has_info
239  ? (fi->faceCentroid() - fi->elemCentroid())
240  : (fi->faceCentroid() - fi->neighborCentroid())));
241  ebf_b.push_back(&elem_value);
242 
243  // eqn. 1
244  grad_ebf_coeffs.push_back(-surface_vector);
245 
246  // eqn. 3
247  if (fdf_face)
248  {
249  // Will be nice in C++17 we'll get a returned reference from this method
250  fdf_grad_centroid_coeffs.emplace_back();
251  auto & current_coeffs = fdf_grad_centroid_coeffs.back();
252  const auto normal = fi->normal();
253  for (const auto i : make_range(lm_dim))
254  for (const auto j : make_range(lm_dim))
255  {
256  auto & current_coeff = current_coeffs(i, j);
257  current_coeff = normal(i) * normal(j);
258  if (i == j)
259  current_coeff -= 1.;
260  }
261  }
262  }
263  else
264  // We are doing a one-term expansion for the extrapolated boundary faces, in which case
265  // we have no eqn. 2 and we have no second term in the LHS of eqn. 1. Instead we apply
266  // the element centroid value as the face value (one-term expansion) in the RHS of eqn.
267  // 1
268  grad_b += surface_vector * elem_value;
269  }
270  else if (isInternalFace(*fi))
271  grad_b +=
272  surface_vector * (*this)(Moose::FaceArg({fi,
274  true,
275  correct_skewness,
276  nullptr,
277  nullptr}),
278  time);
279  else
280  {
281  mooseAssert(isDirichletBoundaryFace(*fi, &functor_elem, time),
282  "We've run out of face types");
283  grad_b += surface_vector * getDirichletBoundaryFaceValue(*fi, &functor_elem, time);
284  }
285 
286  if (!volume_set)
287  {
288  // We use the FaceInfo volumes because those values have been pre-computed and cached.
289  // An explicit call to elem->volume() here would incur unnecessary expense
290  if (elem_has_info)
291  {
293  this->_subproblem, functor_elem.subdomain_id(), fi->elemCentroid(), coord);
294  volume = fi->elemVolume() * coord;
295  }
296  else
297  {
299  this->_subproblem, neighbor->subdomain_id(), fi->neighborCentroid(), coord);
300  volume = fi->neighborVolume() * coord;
301  }
302 
303  volume_set = true;
304  }
305  };
306 
307  Moose::FV::loopOverElemFaceInfo(*elem, this->_mesh, this->_subproblem, action_functor);
308 
309  mooseAssert(volume_set && volume > 0, "We should have set the volume");
310  grad_b /= volume;
311 
312  const auto coord_system = this->_subproblem.getCoordSystem(elem->subdomain_id());
313  if (coord_system == Moose::CoordinateSystemType::COORD_RZ)
314  {
315  const auto r_coord = this->_subproblem.getAxisymmetricRadialCoord();
316  grad_b(r_coord) -= elem_value / elem->vertex_average()(r_coord);
317  }
318 
319  mooseAssert(
320  coord_system != Moose::CoordinateSystemType::COORD_RSPHERICAL,
321  "We have not yet implemented the correct translation from gradient to divergence for "
322  "spherical coordinates yet.");
323 
324  mooseAssert(
325  ebf_faces.size() < UINT_MAX,
326  "You've created a mystical element that has more faces than can be held by unsigned "
327  "int. I applaud you.");
328  const auto num_ebfs = static_cast<unsigned int>(ebf_faces.size());
329 
330  // test for simple case
331  if (num_ebfs == 0)
332  grad = grad_b;
333  else
334  {
335  // We have to solve a system
336  const unsigned int sys_dim =
337  lm_dim + num_ebfs + lm_dim * static_cast<unsigned int>(fdf_grad_centroid_coeffs.size());
338  DenseVector<ADReal> x(sys_dim), b(sys_dim);
339  DenseMatrix<ADReal> A(sys_dim, sys_dim);
340 
341  // eqn. 1
342  for (const auto lm_dim_index : make_range(lm_dim))
343  {
344  // LHS term 1 coeffs
345  A(lm_dim_index, lm_dim_index) = 1;
346 
347  // LHS term 2 coeffs
348  for (const auto ebf_index : make_range(num_ebfs))
349  A(lm_dim_index, lm_dim + ebf_index) = grad_ebf_coeffs[ebf_index](lm_dim_index) / volume;
350 
351  // RHS
352  b(lm_dim_index) = grad_b(lm_dim_index);
353  }
354 
355  unsigned int num_fdf_faces = 0;
356 
357  // eqn. 2
358  for (const auto ebf_index : make_range(num_ebfs))
359  {
360  // LHS term 1 coeffs
361  A(lm_dim + ebf_index, lm_dim + ebf_index) = 1;
362 
363  const bool fdf_face = ebf_faces[ebf_index].second;
364  const unsigned int starting_j_index =
365  fdf_face ? lm_dim + num_ebfs + num_fdf_faces * lm_dim : 0;
366 
367  num_fdf_faces += fdf_face;
368 
369  // LHS term 2 coeffs
370  for (const auto lm_dim_index : make_range(lm_dim))
371  A(lm_dim + ebf_index, starting_j_index + lm_dim_index) =
372  ebf_grad_coeffs[ebf_index](lm_dim_index);
373 
374  // RHS
375  b(lm_dim + ebf_index) = *ebf_b[ebf_index];
376  }
377 
378  mooseAssert(num_fdf_faces == fdf_grad_centroid_coeffs.size(),
379  "Bad math in INSFVVelocityVariable::adGradlnSln(const Elem *). Please contact a "
380  "MOOSE developer");
381 
382  // eqn. 3
383  for (const auto fdf_face_index : make_range(num_fdf_faces))
384  {
385  const auto starting_i_index = lm_dim + num_ebfs + fdf_face_index * lm_dim;
386 
387  for (const auto lm_dim_i_index : make_range(lm_dim))
388  {
389  auto i_index = starting_i_index + lm_dim_i_index;
390  A(i_index, i_index) = 1;
391 
392  for (const auto lm_dim_j_index : make_range(lm_dim))
393  // j_index = lm_dim_j_index
394  A(i_index, lm_dim_j_index) =
395  fdf_grad_centroid_coeffs[fdf_face_index](lm_dim_i_index, lm_dim_j_index);
396  }
397  }
398 
399  A.lu_solve(b, x);
400  for (const auto lm_dim_index : make_range(lm_dim))
401  grad(lm_dim_index) = x(lm_dim_index);
402  }
403 
404  if (_cache_cell_gradients && !correct_skewness)
405  {
406  auto pr = _elem_to_grad.emplace(elem, std::move(grad));
407  mooseAssert(pr.second, "Insertion should have just happened.");
408  return pr.first->second;
409  }
410  else
411  return grad;
412  }
413  catch (libMesh::LogicError &)
414  {
415  // Retry without two-term
416  mooseAssert(_two_term_boundary_expansion,
417  "I believe we should only get singular systems when two-term boundary expansion is "
418  "being used");
419  const_cast<INSFVVelocityVariable *>(this)->_two_term_boundary_expansion = false;
420  const auto & grad = adGradSln(elem, time, correct_skewness);
421 
422  // Two term boundary expansion should only fail at domain corners. We want to keep trying it
423  // at other boundary locations
424  const_cast<INSFVVelocityVariable *>(this)->_two_term_boundary_expansion = true;
425 
426  return grad;
427  }
428 }
void loopOverElemFaceInfo(const Elem &elem, const MooseMesh &mesh, ActionFunctor &act, const Moose::CoordinateSystemType coord_type, const unsigned int rz_radial_coord=libMesh::invalid_uint)
unsigned int getAxisymmetricRadialCoord() const
registerMooseObject("NavierStokesApp", INSFVVelocityVariable)
const bool _cache_cell_gradients
const unsigned int invalid_uint
std::tuple< const Elem *, const Elem *, bool > determineElemOneAndTwo(const FaceInfo &fi, const FVVar &var)
const Elem & elem() const
INSFVVelocityVariable(const InputParameters &params)
const Point & faceCentroid() const
void coordTransformFactor(const SubProblem &s, SubdomainID sub_id, const P &point, C &factor, SubdomainID neighbor_sub_id=libMesh::Elem::invalid_subdomain_id)
MeshBase & mesh
const Point & neighborCentroid() const
VectorValue< ADReal > _temp_cell_gradient
DualNumber< Real, DNDerivativeType, true > ADReal
ADReal getElemValue(const Elem *elem, const StateArg &state) const
bool isExtrapolatedBoundaryFace(const FaceInfo &fi, const Elem *elem, const Moose::StateArg &time) const override
Definition: INSFVVariable.C:92
virtual bool isDirichletBoundaryFace(const FaceInfo &fi, const Elem *elem, const Moose::StateArg &state) const
bool isInternalFace(const FaceInfo &) const
static InputParameters validParams()
void libmesh_ignore(const Args &...)
const Point & elemCentroid() const
virtual VectorValue< ADReal > uncorrectedAdGradSln(const FaceInfo &fi, const StateArg &state, const bool correct_skewness=false) const
SubProblem & _subproblem
const std::vector< double > x
VectorValue< ADReal > uncorrectedAdGradSln(const FaceInfo &fi, const StateArg &time, const bool correct_skewness=false) const override
const Point & normal() const
Real volume(const MeshBase &mesh, unsigned int dim=libMesh::invalid_uint)
virtual ADReal getExtrapolatedBoundaryFaceValue(const FaceInfo &fi, bool two_term_expansion, bool correct_skewness, const Elem *elem_side_to_extrapolate_from, const StateArg &state) const
std::string grad(const std::string &var)
Definition: NS.h:91
static InputParameters validParams()
Definition: INSFVVariable.C:24
ADReal getExtrapolatedBoundaryFaceValue(const FaceInfo &fi, bool two_term_expansion, bool correct_skewness, const Elem *elem_side_to_extrapolate_from, const StateArg &time) const override
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
virtual ADReal getDirichletBoundaryFaceValue(const FaceInfo &fi, const Elem *elem, const Moose::StateArg &state) const
Moose::CoordinateSystemType getCoordSystem(SubdomainID sid) const
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
bool isFullyDevelopedFlowFace(const FaceInfo &fi) const
Returns whether the passed-in FaceInfo corresponds to a fully-developed flow face.
unsigned int state
std::unordered_map< const Elem *, VectorValue< ADReal > > _elem_to_grad
const ADTemplateVariableGradient< Real > & adGradSln() const override