https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
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
35namespace Moose
36{
37namespace FV
38{
39template <typename ActionFunctor>
40void
41loopOverElemFaceInfo(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
58
64
68
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
109VectorValue<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
127const VectorValue<ADReal> &
128INSFVVelocityVariable::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,
275 Moose::FV::LimiterType::CentralDifference,
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
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}
DualNumber< Real, DNDerivativeType, true > ADReal
void coordTransformFactor(const SubProblem &s, SubdomainID sub_id, const P &point, C &factor, SubdomainID neighbor_sub_id=libMesh::Elem::invalid_subdomain_id)
const std::vector< double > x
registerMooseObject("NavierStokesApp", INSFVVelocityVariable)
const Point & normal() const
const Elem & elem() const
const Point & neighborCentroid() const
const Point & elemCentroid() const
const Point & faceCentroid() const
bool isExtrapolatedBoundaryFace(const FaceInfo &fi, const Elem *elem, const Moose::StateArg &time) const override
static InputParameters validParams()
bool isFullyDevelopedFlowFace(const FaceInfo &fi) const
Returns whether the passed-in FaceInfo corresponds to a fully-developed flow face.
VectorValue< ADReal > uncorrectedAdGradSln(const FaceInfo &fi, const StateArg &time, const bool correct_skewness=false) const override
ADReal getExtrapolatedBoundaryFaceValue(const FaceInfo &fi, bool two_term_expansion, bool correct_skewness, const Elem *elem_side_to_extrapolate_from, const StateArg &time) const override
static InputParameters validParams()
INSFVVelocityVariable(const InputParameters &params)
SubProblem & _subproblem
virtual VectorValue< ADReal > uncorrectedAdGradSln(const FaceInfo &fi, const StateArg &state, const bool correct_skewness=false) const
std::unordered_map< const Elem *, VectorValue< ADReal > > _elem_to_grad
virtual bool isDirichletBoundaryFace(const FaceInfo &fi, const Elem *elem, const Moose::StateArg &state) const
virtual ADReal getExtrapolatedBoundaryFaceValue(const FaceInfo &fi, bool two_term_expansion, bool correct_skewness, const Elem *elem_side_to_extrapolate_from, const StateArg &state) const
ADReal getElemValue(const Elem *elem, const StateArg &state) const
virtual ADReal getDirichletBoundaryFaceValue(const FaceInfo &fi, const Elem *elem, const Moose::StateArg &state) const
const bool _cache_cell_gradients
const ADTemplateVariableGradient< OutputType > & adGradSln() const override
VectorValue< ADReal > _temp_cell_gradient
virtual const ADTemplateVariableGradient< OutputType > & adGradSln() const=0
bool isInternalFace(const FaceInfo &) const
unsigned int getAxisymmetricRadialCoord() const
Moose::CoordinateSystemType getCoordSystem(SubdomainID sid) const
MeshBase & mesh
std::tuple< const Elem *, const Elem *, bool > determineElemOneAndTwo(const FaceInfo &fi, const FVVar &var)
void loopOverElemFaceInfo(const Elem &elem, const MooseMesh &mesh, ActionFunctor &act, const Moose::CoordinateSystemType coord_type, const unsigned int rz_radial_coord=libMesh::invalid_uint)
const unsigned int invalid_uint
unsigned int state