https://mooseframework.inl.gov
Loading...
Searching...
No Matches
PorousFlowDarcyBase.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 "PorousFlowDarcyBase.h"
11
12#include "Assembly.h"
13#include "MooseMesh.h"
14#include "MooseVariable.h"
15#include "SystemBase.h"
16
17#include "libmesh/quadrature.h"
18
19template <bool is_ad>
22{
24 params.addRequiredParam<RealVectorValue>("gravity",
25 "Gravitational acceleration vector downwards (m/s^2)");
26 params.addRequiredParam<UserObjectName>(
27 "PorousFlowDictator", "The UserObject that holds the list of PorousFlow variable names");
28 params.addParam<unsigned>("full_upwind_threshold",
29 5,
30 "If, for each timestep, the number of "
31 "upwind-downwind swaps in an element is less than "
32 "this quantity, then full upwinding is used for that element. "
33 "Otherwise the fallback scheme is employed.");
34 MooseEnum fallback_enum("quick harmonic", "quick");
35 params.addParam<MooseEnum>("fallback_scheme",
36 fallback_enum,
37 "quick: use nodal mobility without "
38 "preserving mass. harmonic: use a "
39 "harmonic mean of nodal mobilities "
40 "and preserve fluid mass");
41 params.addClassDescription("Fully-upwinded advective Darcy flux");
42 return params;
43}
44
45template <bool is_ad>
47 : GenericKernel<is_ad>(parameters),
48 _permeability(this->template getGenericMaterialProperty<RealTensorValue, is_ad>(
49 "PorousFlow_permeability_qp")),
50 _dpermeability_dvar(is_ad ? nullptr
51 : &this->template getMaterialProperty<std::vector<RealTensorValue>>(
52 "dPorousFlow_permeability_qp_dvar")),
53 _dpermeability_dgradvar(
54 is_ad ? nullptr
55 : &this->template getMaterialProperty<std::vector<std::vector<RealTensorValue>>>(
56 "dPorousFlow_permeability_qp_dgradvar")),
57 _fluid_density_node(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
58 "PorousFlow_fluid_phase_density_nodal")),
59 _dfluid_density_node_dvar(
60 is_ad ? nullptr
61 : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
62 "dPorousFlow_fluid_phase_density_nodal_dvar")),
63 _fluid_density_qp(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
64 "PorousFlow_fluid_phase_density_qp")),
65 _dfluid_density_qp_dvar(
66 is_ad ? nullptr
67 : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
68 "dPorousFlow_fluid_phase_density_qp_dvar")),
69 _fluid_viscosity(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
70 "PorousFlow_viscosity_nodal")),
71 _dfluid_viscosity_dvar(
72 is_ad ? nullptr
73 : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
74 "dPorousFlow_viscosity_nodal_dvar")),
75 _pp(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
76 "PorousFlow_porepressure_nodal")),
77 _grad_p(this->template getGenericMaterialProperty<std::vector<RealGradient>, is_ad>(
78 "PorousFlow_grad_porepressure_qp")),
79 _dgrad_p_dgrad_var(is_ad ? nullptr
80 : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
81 "dPorousFlow_grad_porepressure_qp_dgradvar")),
82 _dgrad_p_dvar(is_ad
83 ? nullptr
84 : &this->template getMaterialProperty<std::vector<std::vector<RealGradient>>>(
85 "dPorousFlow_grad_porepressure_qp_dvar")),
86 _dictator(this->template getUserObject<PorousFlowDictator>("PorousFlowDictator")),
87 _num_phases(_dictator.numPhases()),
88 _gravity(this->template getParam<RealVectorValue>("gravity")),
89 _perm_derivs(_dictator.usePermDerivs()),
90 _full_upwind_threshold(this->template getParam<unsigned>("full_upwind_threshold")),
91 _fallback_scheme(
92 this->template getParam<MooseEnum>("fallback_scheme").template getEnum<FallbackEnum>()),
93 _proto_flux(_num_phases),
94 _jacobian(_num_phases),
95 _num_upwinds(),
96 _num_downwinds()
97{
98 // The full-upwinding scheme identifies each element test function with a mesh node, which is
99 // only valid for nodal (Lagrange) variables.
100 if (!_var.isNodal())
101 mooseError("The variable '",
102 _var.name(),
103 "' is not a nodal (Lagrange) variable. This kernel uses full upwinding, which "
104 "requires a nodal variable. PorousFlow has no discontinuous-Galerkin "
105 "finite-element discretisation, so an element-local variable (eg CONSTANT "
106 "MONOMIAL) cannot be used as a PorousFlow variable.");
107
108#ifdef LIBMESH_HAVE_TBB_API
109 if (libMesh::n_threads() > 1)
110 mooseWarning("PorousFlowDarcyBase: num_upwinds and num_downwinds may not be computed "
111 "accurately when using TBB and greater than 1 thread");
112#endif
113}
114
115template <bool is_ad>
116void
118{
120 _num_upwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
121 _num_downwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
122}
123
124template <bool is_ad>
125void
131
132template <bool is_ad>
135{
136 return _grad_test[_i][_qp] *
137 (_permeability[_qp] * (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity));
138}
139
140template <bool is_ad>
141Real
142PorousFlowDarcyBaseTempl<is_ad>::darcyQpJacobian(unsigned int jvar, unsigned int ph) const
143{
144 if constexpr (!is_ad)
145 {
146 if (_dictator.notPorousFlowVariable(jvar))
147 return 0.0;
148
149 const unsigned int pvar = _dictator.porousFlowVariableNum(jvar);
150
151 RealVectorValue deriv =
152 _permeability[_qp] * (_grad_phi[_j][_qp] * (*_dgrad_p_dgrad_var)[_qp][ph][pvar] -
153 _phi[_j][_qp] * (*_dfluid_density_qp_dvar)[_qp][ph][pvar] * _gravity);
154
155 deriv += _permeability[_qp] * ((*_dgrad_p_dvar)[_qp][ph][pvar] * _phi[_j][_qp]);
156
157 if (_perm_derivs)
158 {
159 deriv += (*_dpermeability_dvar)[_qp][pvar] * _phi[_j][_qp] *
160 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
161 for (const auto i : make_range(Moose::dim))
162 deriv += (*_dpermeability_dgradvar)[_qp][i][pvar] * _grad_phi[_j][_qp](i) *
163 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
164 }
165
166 return _grad_test[_i][_qp] * deriv;
167 }
168 else
169 libmesh_ignore(jvar, ph);
170 return 0.0;
171}
172
173template <bool is_ad>
176{
177 mooseError("PorousFlowDarcyBase: computeQpResidual called");
178 return 0.0;
179}
180
181template <bool is_ad>
182void
184{
185 if constexpr (!is_ad)
186 computeResidualAndJacobian(JacRes::CALCULATE_RESIDUAL, 0);
187 else
188 {
189 adComputeProtoFlux(false);
190 // Assemble the real-valued residual from the value parts of the ADReal proto fluxes
191 assembleProtoFluxResidual();
192 }
193}
194
195template <bool is_ad>
196void
198{
199 this->prepareVectorTag(this->_assembly, _var.number());
200 for (_i = 0; _i < _test.size(); _i++)
201 for (unsigned int ph = 0; ph < _num_phases; ++ph)
202 this->_local_re(_i) += MetaPhysicL::raw_value(_proto_flux[ph][_i]);
203 this->accumulateTaggedLocalResidual();
204
205 if (this->_has_save_in)
206 for (unsigned int i = 0; i < this->_save_in.size(); i++)
207 this->_save_in[i]->sys().solution().add_vector(this->_local_re,
208 this->_save_in[i]->dofIndices());
209}
210
211template <bool is_ad>
212void
214{
215 if constexpr (!is_ad)
216 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, _var.number());
217 else
218 // Block-diagonal (e.g. PJFNK) assembly path: this is the only Jacobian call we get.
219 adComputeJacobian();
220}
221
222template <bool is_ad>
223void
225{
226 if constexpr (is_ad)
227 {
228 // Compute ADReal proto fluxes with upwind counting, then extract the Jacobian.
229 // Every block (diagonal and off-diagonal) is encoded in the ADReal derivatives, so a
230 // single call to addJacobianWithoutConstraints captures all variable sensitivities.
231 //
232 // See PorousFlowLumpedKernelBase.C::computeJacobian for the detailed rationale of why
233 // addJacobianWithoutConstraints (rather than the default ADKernel addJacobian) is required:
234 // each residual row's column set must be read from its own derivatives instead of being shared
235 // from row 0. There it is forced by mass-lumped nodal materials; here it is forced by the
236 // upwinding scheme, which makes each node's proto flux depend on a different set of nodal DOFs.
237 adComputeProtoFlux(true);
238
239 const unsigned int num_nodes = _test.size();
240 std::vector<ADReal> darcy_residuals(num_nodes, 0.0);
241 for (const auto n : make_range(num_nodes))
242 for (const auto ph : make_range(_num_phases))
243 darcy_residuals[n] += _proto_flux[ph][n];
244
245 this->addJacobianWithoutConstraints(
246 this->_assembly, darcy_residuals, this->dofIndices(), _var.scalingFactor());
247 }
248}
249
250template <bool is_ad>
251void
253{
254 if constexpr (!is_ad)
255 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, jvar);
256 else
257 {
258 libmesh_ignore(jvar);
259 // Full (SMP) assembly calls this once per coupled variable; the first call performs the
260 // single AD pass that fills every block, and the guard makes the rest no-ops. This mirrors
261 // ADKernel::computeOffDiagJacobian and avoids the ADD-semantics double-counting.
262 if (_my_elem_darcy != this->_current_elem)
263 {
264 adComputeJacobian();
265 _my_elem_darcy = this->_current_elem;
266 }
267 }
268}
269
270template <bool is_ad>
271void
273{
274 const unsigned int num_nodes = _test.size();
275 for (unsigned ph = 0; ph < _num_phases; ++ph)
276 {
277 _proto_flux[ph].assign(num_nodes, 0.0);
278 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
279 {
280 const Real jxw_coord = this->_JxW[_qp] * this->_coord[_qp];
281 for (_i = 0; _i < num_nodes; ++_i)
282 _proto_flux[ph][_i] += jxw_coord * darcyQp(ph);
283 }
284 }
285}
286
287template <bool is_ad>
288void
290{
291 if (_num_upwinds.find(elem) == _num_upwinds.end())
292 {
293 _num_upwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
294 _num_downwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
295 for (unsigned ph = 0; ph < _num_phases; ++ph)
296 {
297 _num_upwinds[elem][ph].assign(num_nodes, 0);
298 _num_downwinds[elem][ph].assign(num_nodes, 0);
299 }
300 }
301}
302
303template <bool is_ad>
304void
305PorousFlowDarcyBaseTempl<is_ad>::updateUpwindCounts(unsigned elem, unsigned int num_nodes)
306{
307 for (unsigned ph = 0; ph < _num_phases; ++ph)
308 for (unsigned nod = 0; nod < num_nodes; ++nod)
309 {
310 if (_proto_flux[ph][nod] > 0)
311 _num_upwinds[elem][ph][nod]++;
312 else if (_proto_flux[ph][nod] < 0)
313 _num_downwinds[elem][ph][nod]++;
314 }
315}
316
317template <bool is_ad>
318std::vector<unsigned>
319PorousFlowDarcyBaseTempl<is_ad>::computeMaxSwaps(unsigned elem, unsigned int num_nodes) const
320{
321 std::vector<unsigned> max_swaps(_num_phases, 0);
322 for (unsigned ph = 0; ph < _num_phases; ++ph)
323 for (unsigned nod = 0; nod < num_nodes; ++nod)
324 max_swaps[ph] =
325 std::max(max_swaps[ph],
326 std::min(_num_upwinds.at(elem)[ph][nod], _num_downwinds.at(elem)[ph][nod]));
327 return max_swaps;
328}
329
330template <bool is_ad>
331void
332PorousFlowDarcyBaseTempl<is_ad>::applyUpwinding(const std::vector<unsigned> & max_swaps,
333 JacRes res_or_jac,
334 unsigned int pvar)
335{
336 for (unsigned int ph = 0; ph < _num_phases; ++ph)
337 {
338 if (max_swaps[ph] < _full_upwind_threshold)
339 fullyUpwind(res_or_jac, ph, pvar);
340 else
341 {
342 switch (_fallback_scheme)
343 {
344 case FallbackEnum::QUICK:
345 quickUpwind(res_or_jac, ph, pvar);
346 break;
347 case FallbackEnum::HARMONIC:
348 harmonicMean(res_or_jac, ph, pvar);
349 break;
350 }
351 }
352 }
353}
354
355template <bool is_ad>
356void
358{
359 const unsigned int num_nodes = _test.size();
360 computeProtoFluxWithoutMobility();
361
362 // Initialise upwind-tracking maps for this element on first encounter
363 const unsigned elem = this->_current_elem->id();
364 initializeUpwindTracking(elem, num_nodes);
365
366 if (do_counting)
367 updateUpwindCounts(elem, num_nodes);
368
369 // Determine how many upwind-downwind swaps have occurred this timestep
370 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
371
372 // Apply mobility via the chosen upwinding scheme (always in residual mode for AD)
373 applyUpwinding(max_swaps, JacRes::CALCULATE_RESIDUAL, 0);
374}
375
376template <bool is_ad>
377void
379{
380 if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _dictator.notPorousFlowVariable(jvar))
381 return;
382
383 // The PorousFlow variable index corresponding to the variable number jvar
384 const unsigned int pvar =
385 ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _dictator.porousFlowVariableNum(jvar) : 0);
386
387 this->prepareMatrixTag(this->_assembly, _var.number(), jvar);
388 if ((this->_local_ke.n() == 0) &&
389 (res_or_jac == JacRes::CALCULATE_JACOBIAN)) // this removes a problem
390 // encountered in the
391 // initial timestep when
392 // use_displaced_mesh=true
393 return;
394
395 // The number of nodes in the element
396 const unsigned int num_nodes = _test.size();
397
398 // Compute the residual and jacobian without the mobility terms. Even if we are computing the
399 // Jacobian we still need this in order to see which nodes are upwind and which are downwind.
400 computeProtoFluxWithoutMobility();
401
402 // for this element, record whether each node is "upwind" or "downwind" (or neither)
403 const unsigned elem = this->_current_elem->id();
404 initializeUpwindTracking(elem, num_nodes);
405 // record the information once per nonlinear iteration
406 if (res_or_jac == JacRes::CALCULATE_JACOBIAN && jvar == _var.number())
407 updateUpwindCounts(elem, num_nodes);
408
409 // based on _num_upwinds and _num_downwinds, calculate the maximum number
410 // of upwind-downwind swaps that have been encountered in this timestep
411 // for this element
412 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
413
414 // size the _jacobian correctly and calculate it for the case residual = _proto_flux
415 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
416 {
417 for (unsigned ph = 0; ph < _num_phases; ++ph)
418 {
419 _jacobian[ph].resize(this->_local_ke.m());
420 for (_i = 0; _i < _test.size(); _i++)
421 {
422 _jacobian[ph][_i].assign(this->_local_ke.n(), 0.0);
423 for (_j = 0; _j < _phi.size(); _j++)
424 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
425 _jacobian[ph][_i][_j] +=
426 this->_JxW[_qp] * this->_coord[_qp] * darcyQpJacobian(jvar, ph);
427 }
428 }
429 }
430
431 // Loop over all the phases, computing the mass flux, which
432 // gets placed into _proto_flux, and the derivative of this
433 // which gets placed into _jacobian
434 applyUpwinding(max_swaps, res_or_jac, pvar);
435
436 // Add results to the Residual or Jacobian
437 if (res_or_jac == JacRes::CALCULATE_RESIDUAL)
438 assembleProtoFluxResidual();
439
440 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
441 {
442 for (_i = 0; _i < _test.size(); _i++)
443 for (_j = 0; _j < _phi.size(); _j++)
444 for (unsigned int ph = 0; ph < _num_phases; ++ph)
445 this->_local_ke(_i, _j) += _jacobian[ph][_i][_j];
446
447 this->accumulateTaggedLocalMatrix();
448
449 if (this->_has_diag_save_in && jvar == _var.number())
450 {
451 unsigned int rows = this->_local_ke.m();
452 DenseVector<Number> diag(rows);
453 for (unsigned int i = 0; i < rows; i++)
454 diag(i) = this->_local_ke(i, i);
455
456 for (unsigned int i = 0; i < this->_diag_save_in.size(); i++)
457 this->_diag_save_in[i]->sys().solution().add_vector(diag,
458 this->_diag_save_in[i]->dofIndices());
459 }
460 }
461}
462
463template <bool is_ad>
464void
465PorousFlowDarcyBaseTempl<is_ad>::fullyUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
466{
467 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
468 if constexpr (is_ad)
469 libmesh_ignore(res_or_jac, pvar);
470
500 // The number of nodes in the element
501 const unsigned int num_nodes = _test.size();
502
504 // Define variables used to ensure mass conservation
505 GenericReal<is_ad> total_mass_out = 0.0;
506 GenericReal<is_ad> total_in = 0.0;
507
508 // The following holds derivatives of these (non-AD path only)
509 std::vector<Real> dtotal_mass_out;
510 std::vector<Real> dtotal_in;
511 if constexpr (!is_ad)
512 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
513 {
514 dtotal_mass_out.assign(num_nodes, 0.0);
515 dtotal_in.assign(num_nodes, 0.0);
516 }
517
518 // Perform the upwinding using the mobility
519 std::vector<bool> upwind_node(num_nodes);
520 for (unsigned int n = 0; n < num_nodes; ++n)
521 {
522 if (_proto_flux[ph][n] >= 0.0) // upstream node
523 {
524 upwind_node[n] = true;
525 // The mobility at the upstream node
526 mob = mobility(n, ph);
527 if constexpr (!is_ad)
528 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
529 {
530 // The derivative of the mobility wrt the PorousFlow variable
531 const Real dmob = dmobility(n, ph, pvar);
532
533 for (_j = 0; _j < _phi.size(); _j++)
534 _jacobian[ph][n][_j] *= mob;
535
536 if (_test.size() == _phi.size())
537 /* mobility at node=n depends only on the variables at node=n, by construction. For
538 * linear-lagrange variables, this means that Jacobian entries involving the derivative
539 * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the
540 * [n][n] in the line below. However, for other variable types (eg constant monomials)
541 * I cannot tell what variable number contributes to the derivative. However, in all
542 * cases I can possibly imagine, the derivative is zero anyway, since in the full
543 * upwinding scheme, mobility shouldn't depend on these other sorts of variables.
544 */
545 _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
546
547 for (_j = 0; _j < _phi.size(); _j++)
548 dtotal_mass_out[_j] += _jacobian[ph][n][_j];
549 }
550 _proto_flux[ph][n] *= mob;
551 total_mass_out += _proto_flux[ph][n];
552 }
553 else
554 {
555 upwind_node[n] = false;
556 total_in -= _proto_flux[ph][n];
557 if constexpr (!is_ad)
558 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
559 for (_j = 0; _j < _phi.size(); _j++)
560 dtotal_in[_j] -= _jacobian[ph][n][_j];
561 }
562 }
563
564 // Conserve mass over all phases by proportioning the total_mass_out mass to the inflow nodes,
565 // weighted by their proto_flux values
566 for (unsigned int n = 0; n < num_nodes; ++n)
567 {
568 if (!upwind_node[n]) // downstream node
569 {
570 if constexpr (!is_ad)
571 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
572 for (_j = 0; _j < _phi.size(); _j++)
573 {
574 _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(total_mass_out / total_in);
575 _jacobian[ph][n][_j] +=
576 MetaPhysicL::raw_value(_proto_flux[ph][n]) *
577 (dtotal_mass_out[_j] / MetaPhysicL::raw_value(total_in) -
578 dtotal_in[_j] * MetaPhysicL::raw_value(total_mass_out) /
579 MetaPhysicL::raw_value(total_in) / MetaPhysicL::raw_value(total_in));
580 }
581 _proto_flux[ph][n] *= total_mass_out / total_in;
582 }
583 }
584}
585
586template <bool is_ad>
587void
588PorousFlowDarcyBaseTempl<is_ad>::quickUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
589{
590 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
591 if constexpr (is_ad)
592 libmesh_ignore(res_or_jac, pvar);
593
594 // The number of nodes in the element
595 const unsigned int num_nodes = _test.size();
596
597 // Use the raw nodal mobility
598 for (unsigned int n = 0; n < num_nodes; ++n)
599 {
600 // The mobility at the node
601 const GenericReal<is_ad> mob = mobility(n, ph);
602 if constexpr (!is_ad)
603 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
604 {
605 // The derivative of the mobility wrt the PorousFlow variable
606 const Real dmob = dmobility(n, ph, pvar);
607
608 for (_j = 0; _j < _phi.size(); _j++)
609 _jacobian[ph][n][_j] *= mob;
610
611 if (_test.size() == _phi.size())
612 /* mobility at node=n depends only on the variables at node=n, by construction. For
613 * linear-lagrange variables, this means that Jacobian entries involving the derivative
614 * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the
615 * [n][n] in the line below. However, for other variable types (eg constant monomials)
616 * I cannot tell what variable number contributes to the derivative. However, in all
617 * cases I can possibly imagine, the derivative is zero anyway, since in the full
618 * upwinding scheme, mobility shouldn't depend on these other sorts of variables.
619 */
620 _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
621 }
622 _proto_flux[ph][n] *= mob;
623 }
624}
625
626template <bool is_ad>
627void
628PorousFlowDarcyBaseTempl<is_ad>::harmonicMean(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
629{
630 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
631 if constexpr (is_ad)
632 libmesh_ignore(res_or_jac, pvar);
633
634 // The number of nodes in the element
635 const unsigned int num_nodes = _test.size();
636
637 std::vector<GenericReal<is_ad>> mob(num_nodes);
638 unsigned num_zero = 0;
639 GenericReal<is_ad> harmonic_mob = 0;
640 for (unsigned n = 0; n < num_nodes; ++n)
641 {
642 mob[n] = mobility(n, ph);
643 if (MetaPhysicL::raw_value(mob[n]) == 0.0)
644 {
645 num_zero++;
646 }
647 else
648 harmonic_mob += 1.0 / mob[n];
649 }
650 if (num_zero > 0)
651 harmonic_mob = 0.0;
652 else
653 harmonic_mob = (1.0 * num_nodes) / harmonic_mob;
654
655 // d(harmonic_mob)/d(PorousFlow variable at node n) -- non-AD path only
656 std::vector<Real> dharmonic_mob(num_nodes, 0.0);
657 if constexpr (!is_ad)
658 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
659 {
660 const Real harm2 = std::pow(MetaPhysicL::raw_value(harmonic_mob), 2) / (1.0 * num_nodes);
661 if (num_zero == 0)
662 for (unsigned n = 0; n < num_nodes; ++n)
663 dharmonic_mob[n] =
664 dmobility(n, ph, pvar) * harm2 / std::pow(MetaPhysicL::raw_value(mob[n]), 2);
665 else if (num_zero == 1)
666 for (unsigned n = 0; n < num_nodes; ++n)
667 if (MetaPhysicL::raw_value(mob[n]) == 0.0)
668 {
669 dharmonic_mob[n] = num_nodes * dmobility(n, ph, pvar); // other derivs are zero
670 break;
671 }
672 // if num_zero > 1 then all dharmonic_mob = 0.0
673 }
674
675 if constexpr (!is_ad)
676 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
677 for (unsigned n = 0; n < num_nodes; ++n)
678 for (_j = 0; _j < _phi.size(); _j++)
679 {
680 _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(harmonic_mob);
681 if (_test.size() == _phi.size())
682 _jacobian[ph][n][_j] += dharmonic_mob[_j] * MetaPhysicL::raw_value(_proto_flux[ph][n]);
683 }
684
685 for (unsigned n = 0; n < num_nodes; ++n)
686 _proto_flux[ph][n] *= harmonic_mob;
687}
688
689template <bool is_ad>
691PorousFlowDarcyBaseTempl<is_ad>::mobility(unsigned nodenum, unsigned phase) const
692{
693 return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
694}
695
696template <bool is_ad>
697Real
698PorousFlowDarcyBaseTempl<is_ad>::dmobility(unsigned nodenum, unsigned phase, unsigned pvar) const
699{
700 if constexpr (!is_ad)
701 {
702 Real dm = (*_dfluid_density_node_dvar)[nodenum][phase][pvar] / _fluid_viscosity[nodenum][phase];
703 dm -= _fluid_density_node[nodenum][phase] * (*_dfluid_viscosity_dvar)[nodenum][phase][pvar] /
704 std::pow(_fluid_viscosity[nodenum][phase], 2);
705 return dm;
706 }
707 else
708 libmesh_ignore(nodenum, phase, pvar);
709 return 0.0;
710}
711
713template class PorousFlowDarcyBaseTempl<true>;
void mooseError(Args &&... args)
Moose::GenericType< Real, is_ad > GenericReal
PetscErrorCode PetscOptionItems *PetscErrorCode DM dm
static InputParameters validParams()
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
MooseVariable & _var
const std::string & name() const
void mooseError(Args &&... args) const
void mooseWarning(Args &&... args) const
bool isNodal() const override
Darcy advective flux.
virtual Real darcyQpJacobian(unsigned int jvar, unsigned int ph) const
Jacobian of the Darcy part of the flux – non-AD path only.
void updateUpwindCounts(unsigned elem, unsigned int num_nodes)
Update upwind/downwind counters from the sign of _proto_flux.
void adComputeProtoFlux(bool do_counting)
For the AD path: fills _proto_flux (per-phase, per-node ADReal) by integrating darcyQp and applying t...
virtual void computeResidualAndJacobian()
virtual Real dmobility(unsigned nodenum, unsigned phase, unsigned pvar) const
The derivative of mobility with respect to PorousFlow variable pvar – non-AD path only.
std::vector< unsigned > computeMaxSwaps(unsigned elem, unsigned int num_nodes) const
Compute per-phase maximum upwind/downwind swap counts for this element.
void applyUpwinding(const std::vector< unsigned > &max_swaps, JacRes res_or_jac, unsigned int pvar)
Apply selected upwinding/fallback scheme for all phases.
void initializeUpwindTracking(unsigned elem, unsigned int num_nodes)
Ensure per-element upwind/downwind counters are allocated for this element.
static InputParameters validParams()
virtual void computeResidual() override
virtual GenericReal< is_ad > computeQpResidual() override
void quickUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
Calculate the residual or Jacobian using the nodal mobilities, but without conserving fluid mass.
virtual GenericReal< is_ad > mobility(unsigned nodenum, unsigned phase) const
The mobility of the fluid.
virtual void timestepSetup() override
virtual void computeOffDiagJacobian(unsigned int jvar) override
void assembleProtoFluxResidual()
Assemble the real-valued residual vector from _proto_flux into the tagged local residual (and apply s...
void fullyUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
Calculate the residual or Jacobian using full upwinding.
PorousFlowDarcyBaseTempl(const InputParameters &parameters)
void adComputeJacobian()
For the AD path: performs the proto-flux/upwinding pass and hands the per-node ADReal residuals to ad...
void harmonicMean(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
Calculate the residual or Jacobian by using the harmonic mean of the nodal mobilities for the entire ...
void computeProtoFluxWithoutMobility()
Build proto fluxes (without mobility weighting) for all phases and nodes.
virtual void computeJacobian() override
FallbackEnum
If full upwinding is failing due to nodes swapping between upwind and downwind in successive nonlinea...
virtual GenericReal< is_ad > darcyQp(unsigned int ph) const
The Darcy part of the flux (this is the non-upwinded part)
virtual void jacobianSetup() override
This holds maps between the nonlinear variables used in a PorousFlow simulation and the variable numb...
virtual void jacobianSetup()
virtual void timestepSetup()
auto raw_value(const Eigen::Map< T > &in)
static constexpr std::size_t dim
unsigned int n_threads()