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