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 (this->_fe_problem.numThreads() > 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 computeResidualOrJacobian(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 computeResidualOrJacobian(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 computeResidualOrJacobian(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 // The AD weight carries the displacement derivatives on a displaced mesh. Elsewhere use the
281 // Real weight, as ADKernel does: XFEM rescales only the Real weight.
282 GenericReal<is_ad> jxw_coord = this->_JxW[_qp] * this->_coord[_qp];
283 if constexpr (is_ad)
284 if (this->_use_displaced_mesh)
285 jxw_coord = this->_ad_JxW[_qp] * this->_ad_coord[_qp];
286 for (_i = 0; _i < num_nodes; ++_i)
287 _proto_flux[ph][_i] += jxw_coord * darcyQp(ph);
288 }
289 }
290}
291
292template <bool is_ad>
293void
295{
296 if (_num_upwinds.find(elem) == _num_upwinds.end())
297 {
298 _num_upwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
299 _num_downwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
300 for (unsigned ph = 0; ph < _num_phases; ++ph)
301 {
302 _num_upwinds[elem][ph].assign(num_nodes, 0);
303 _num_downwinds[elem][ph].assign(num_nodes, 0);
304 }
305 }
306}
307
308template <bool is_ad>
309void
310PorousFlowDarcyBaseTempl<is_ad>::updateUpwindCounts(unsigned elem, unsigned int num_nodes)
311{
312 for (unsigned ph = 0; ph < _num_phases; ++ph)
313 for (unsigned nod = 0; nod < num_nodes; ++nod)
314 {
315 if (_proto_flux[ph][nod] > 0)
316 _num_upwinds[elem][ph][nod]++;
317 else if (_proto_flux[ph][nod] < 0)
318 _num_downwinds[elem][ph][nod]++;
319 }
320}
321
322template <bool is_ad>
323std::vector<unsigned>
324PorousFlowDarcyBaseTempl<is_ad>::computeMaxSwaps(unsigned elem, unsigned int num_nodes) const
325{
326 std::vector<unsigned> max_swaps(_num_phases, 0);
327 for (unsigned ph = 0; ph < _num_phases; ++ph)
328 for (unsigned nod = 0; nod < num_nodes; ++nod)
329 max_swaps[ph] =
330 std::max(max_swaps[ph],
331 std::min(_num_upwinds.at(elem)[ph][nod], _num_downwinds.at(elem)[ph][nod]));
332 return max_swaps;
333}
334
335template <bool is_ad>
336void
337PorousFlowDarcyBaseTempl<is_ad>::applyUpwinding(const std::vector<unsigned> & max_swaps,
338 JacRes res_or_jac,
339 unsigned int pvar)
340{
341 for (unsigned int ph = 0; ph < _num_phases; ++ph)
342 {
343 if (max_swaps[ph] < _full_upwind_threshold)
344 fullyUpwind(res_or_jac, ph, pvar);
345 else
346 {
347 switch (_fallback_scheme)
348 {
349 case FallbackEnum::QUICK:
350 quickUpwind(res_or_jac, ph, pvar);
351 break;
352 case FallbackEnum::HARMONIC:
353 harmonicMean(res_or_jac, ph, pvar);
354 break;
355 }
356 }
357 }
358}
359
360template <bool is_ad>
361void
363{
364 const unsigned int num_nodes = _test.size();
365 computeProtoFluxWithoutMobility();
366
367 // Initialise upwind-tracking maps for this element on first encounter
368 const unsigned elem = this->_current_elem->id();
369 initializeUpwindTracking(elem, num_nodes);
370
371 if (do_counting)
372 updateUpwindCounts(elem, num_nodes);
373
374 // Determine how many upwind-downwind swaps have occurred this timestep
375 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
376
377 // Apply mobility via the chosen upwinding scheme (always in residual mode for AD)
378 applyUpwinding(max_swaps, JacRes::CALCULATE_RESIDUAL, 0);
379}
380
381template <bool is_ad>
382void
384{
385 if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _dictator.notPorousFlowVariable(jvar))
386 return;
387
388 // The PorousFlow variable index corresponding to the variable number jvar
389 const unsigned int pvar =
390 ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _dictator.porousFlowVariableNum(jvar) : 0);
391
392 this->prepareMatrixTag(this->_assembly, _var.number(), jvar);
393 if ((this->_local_ke.n() == 0) &&
394 (res_or_jac == JacRes::CALCULATE_JACOBIAN)) // this removes a problem
395 // encountered in the
396 // initial timestep when
397 // use_displaced_mesh=true
398 return;
399
400 // The number of nodes in the element
401 const unsigned int num_nodes = _test.size();
402
403 // Compute the residual and jacobian without the mobility terms. Even if we are computing the
404 // Jacobian we still need this in order to see which nodes are upwind and which are downwind.
405 computeProtoFluxWithoutMobility();
406
407 // for this element, record whether each node is "upwind" or "downwind" (or neither)
408 const unsigned elem = this->_current_elem->id();
409 initializeUpwindTracking(elem, num_nodes);
410 // record the information once per nonlinear iteration
411 if (res_or_jac == JacRes::CALCULATE_JACOBIAN && jvar == _var.number())
412 updateUpwindCounts(elem, num_nodes);
413
414 // based on _num_upwinds and _num_downwinds, calculate the maximum number
415 // of upwind-downwind swaps that have been encountered in this timestep
416 // for this element
417 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
418
419 // size the _jacobian correctly and calculate it for the case residual = _proto_flux
420 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
421 {
422 for (unsigned ph = 0; ph < _num_phases; ++ph)
423 {
424 _jacobian[ph].resize(this->_local_ke.m());
425 for (_i = 0; _i < _test.size(); _i++)
426 {
427 _jacobian[ph][_i].assign(this->_local_ke.n(), 0.0);
428 for (_j = 0; _j < _phi.size(); _j++)
429 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
430 _jacobian[ph][_i][_j] +=
431 this->_JxW[_qp] * this->_coord[_qp] * darcyQpJacobian(jvar, ph);
432 }
433 }
434 }
435
436 // Loop over all the phases, computing the mass flux, which
437 // gets placed into _proto_flux, and the derivative of this
438 // which gets placed into _jacobian
439 applyUpwinding(max_swaps, res_or_jac, pvar);
440
441 // Add results to the Residual or Jacobian
442 if (res_or_jac == JacRes::CALCULATE_RESIDUAL)
443 assembleProtoFluxResidual();
444
445 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
446 {
447 for (_i = 0; _i < _test.size(); _i++)
448 for (_j = 0; _j < _phi.size(); _j++)
449 for (unsigned int ph = 0; ph < _num_phases; ++ph)
450 this->_local_ke(_i, _j) += _jacobian[ph][_i][_j];
451
452 this->accumulateTaggedLocalMatrix();
453
454 if (this->_has_diag_save_in && jvar == _var.number())
455 {
456 unsigned int rows = this->_local_ke.m();
457 DenseVector<Number> diag(rows);
458 for (unsigned int i = 0; i < rows; i++)
459 diag(i) = this->_local_ke(i, i);
460
461 for (unsigned int i = 0; i < this->_diag_save_in.size(); i++)
462 this->_diag_save_in[i]->sys().solution().add_vector(diag,
463 this->_diag_save_in[i]->dofIndices());
464 }
465 }
466}
467
468template <bool is_ad>
469void
470PorousFlowDarcyBaseTempl<is_ad>::fullyUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
471{
472 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
473 if constexpr (is_ad)
474 libmesh_ignore(res_or_jac, pvar);
475
505 // The number of nodes in the element
506 const unsigned int num_nodes = _test.size();
507
509 // Define variables used to ensure mass conservation
510 GenericReal<is_ad> total_mass_out = 0.0;
511 GenericReal<is_ad> total_in = 0.0;
512
513 // The following holds derivatives of these (non-AD path only)
514 std::vector<Real> dtotal_mass_out;
515 std::vector<Real> dtotal_in;
516
517 // These accumulate Jacobian columns, so they must be sized by the column variable's
518 // DOF count (_phi.size(), not by num_nodes = _test.size() (which is the row variable's)
519 if constexpr (!is_ad)
520 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
521 {
522 dtotal_mass_out.assign(_phi.size(), 0.0);
523 dtotal_in.assign(_phi.size(), 0.0);
524 }
525
526 // Perform the upwinding using the mobility
527 std::vector<bool> upwind_node(num_nodes);
528 for (unsigned int n = 0; n < num_nodes; ++n)
529 {
530 if (_proto_flux[ph][n] >= 0.0) // upstream node
531 {
532 upwind_node[n] = true;
533 // The mobility at the upstream node
534 mob = mobility(n, ph);
535 if constexpr (!is_ad)
536 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
537 {
538 // The derivative of the mobility wrt the PorousFlow variable
539 const Real dmob = dmobility(n, ph, pvar);
540
541 for (_j = 0; _j < _phi.size(); _j++)
542 _jacobian[ph][n][_j] *= mob;
543
544 if (_test.size() == _phi.size())
545 /* mobility at node=n depends only on the variables at node=n, by construction. For
546 * linear-lagrange variables, this means that Jacobian entries involving the derivative
547 * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the
548 * [n][n] in the line below. However, for other variable types (eg constant monomials)
549 * I cannot tell what variable number contributes to the derivative. However, in all
550 * cases I can possibly imagine, the derivative is zero anyway, since in the full
551 * upwinding scheme, mobility shouldn't depend on these other sorts of variables.
552 */
553 _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
554
555 for (_j = 0; _j < _phi.size(); _j++)
556 dtotal_mass_out[_j] += _jacobian[ph][n][_j];
557 }
558 _proto_flux[ph][n] *= mob;
559 total_mass_out += _proto_flux[ph][n];
560 }
561 else
562 {
563 upwind_node[n] = false;
564 total_in -= _proto_flux[ph][n];
565 if constexpr (!is_ad)
566 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
567 for (_j = 0; _j < _phi.size(); _j++)
568 dtotal_in[_j] -= _jacobian[ph][n][_j];
569 }
570 }
571
572 // Conserve mass over all phases by proportioning the total_mass_out mass to the inflow nodes,
573 // weighted by their proto_flux values
574 for (unsigned int n = 0; n < num_nodes; ++n)
575 {
576 if (!upwind_node[n]) // downstream node
577 {
578 if constexpr (!is_ad)
579 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
580 for (_j = 0; _j < _phi.size(); _j++)
581 {
582 _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(total_mass_out / total_in);
583 _jacobian[ph][n][_j] +=
584 MetaPhysicL::raw_value(_proto_flux[ph][n]) *
585 (dtotal_mass_out[_j] / MetaPhysicL::raw_value(total_in) -
586 dtotal_in[_j] * MetaPhysicL::raw_value(total_mass_out) /
587 MetaPhysicL::raw_value(total_in) / MetaPhysicL::raw_value(total_in));
588 }
589 _proto_flux[ph][n] *= total_mass_out / total_in;
590 }
591 }
592}
593
594template <bool is_ad>
595void
596PorousFlowDarcyBaseTempl<is_ad>::quickUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
597{
598 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
599 if constexpr (is_ad)
600 libmesh_ignore(res_or_jac, pvar);
601
602 // The number of nodes in the element
603 const unsigned int num_nodes = _test.size();
604
605 // Use the raw nodal mobility
606 for (unsigned int n = 0; n < num_nodes; ++n)
607 {
608 // The mobility at the node
609 const GenericReal<is_ad> mob = mobility(n, ph);
610 if constexpr (!is_ad)
611 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
612 {
613 // The derivative of the mobility wrt the PorousFlow variable
614 const Real dmob = dmobility(n, ph, pvar);
615
616 for (_j = 0; _j < _phi.size(); _j++)
617 _jacobian[ph][n][_j] *= mob;
618
619 if (_test.size() == _phi.size())
620 /* mobility at node=n depends only on the variables at node=n, by construction. For
621 * linear-lagrange variables, this means that Jacobian entries involving the derivative
622 * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the
623 * [n][n] in the line below. However, for other variable types (eg constant monomials)
624 * I cannot tell what variable number contributes to the derivative. However, in all
625 * cases I can possibly imagine, the derivative is zero anyway, since in the full
626 * upwinding scheme, mobility shouldn't depend on these other sorts of variables.
627 */
628 _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
629 }
630 _proto_flux[ph][n] *= mob;
631 }
632}
633
634template <bool is_ad>
635void
636PorousFlowDarcyBaseTempl<is_ad>::harmonicMean(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
637{
638 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
639 if constexpr (is_ad)
640 libmesh_ignore(res_or_jac, pvar);
641
642 // The number of nodes in the element
643 const unsigned int num_nodes = _test.size();
644
645 std::vector<GenericReal<is_ad>> mob(num_nodes);
646 unsigned num_zero = 0;
647 GenericReal<is_ad> harmonic_mob = 0;
648 for (unsigned n = 0; n < num_nodes; ++n)
649 {
650 mob[n] = mobility(n, ph);
651 if (MetaPhysicL::raw_value(mob[n]) == 0.0)
652 {
653 num_zero++;
654 }
655 else
656 harmonic_mob += 1.0 / mob[n];
657 }
658 if (num_zero > 0)
659 harmonic_mob = 0.0;
660 else
661 harmonic_mob = (1.0 * num_nodes) / harmonic_mob;
662
663 // d(harmonic_mob)/d(PorousFlow variable at node n) -- non-AD path only
664 std::vector<Real> dharmonic_mob(num_nodes, 0.0);
665 if constexpr (!is_ad)
666 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
667 {
668 const Real harm2 = std::pow(MetaPhysicL::raw_value(harmonic_mob), 2) / (1.0 * num_nodes);
669 if (num_zero == 0)
670 for (unsigned n = 0; n < num_nodes; ++n)
671 dharmonic_mob[n] =
672 dmobility(n, ph, pvar) * harm2 / std::pow(MetaPhysicL::raw_value(mob[n]), 2);
673 else if (num_zero == 1)
674 for (unsigned n = 0; n < num_nodes; ++n)
675 if (MetaPhysicL::raw_value(mob[n]) == 0.0)
676 {
677 dharmonic_mob[n] = num_nodes * dmobility(n, ph, pvar); // other derivs are zero
678 break;
679 }
680 // if num_zero > 1 then all dharmonic_mob = 0.0
681 }
682
683 if constexpr (!is_ad)
684 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
685 for (unsigned n = 0; n < num_nodes; ++n)
686 for (_j = 0; _j < _phi.size(); _j++)
687 {
688 _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(harmonic_mob);
689 if (_test.size() == _phi.size())
690 _jacobian[ph][n][_j] += dharmonic_mob[_j] * MetaPhysicL::raw_value(_proto_flux[ph][n]);
691 }
692
693 for (unsigned n = 0; n < num_nodes; ++n)
694 _proto_flux[ph][n] *= harmonic_mob;
695}
696
697template <bool is_ad>
699PorousFlowDarcyBaseTempl<is_ad>::mobility(unsigned nodenum, unsigned phase) const
700{
701 return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
702}
703
704template <bool is_ad>
705Real
706PorousFlowDarcyBaseTempl<is_ad>::dmobility(unsigned nodenum, unsigned phase, unsigned pvar) const
707{
708 if constexpr (!is_ad)
709 {
710 Real dm = (*_dfluid_density_node_dvar)[nodenum][phase][pvar] / _fluid_viscosity[nodenum][phase];
711 dm -= _fluid_density_node[nodenum][phase] * (*_dfluid_viscosity_dvar)[nodenum][phase][pvar] /
712 std::pow(_fluid_viscosity[nodenum][phase], 2);
713 return dm;
714 }
715 else
716 libmesh_ignore(nodenum, phase, pvar);
717 return 0.0;
718}
719
721template 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 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 computeResidualOrJacobian(JacRes res_or_jac, unsigned int jvar)
Computation of the residual or the Jacobian, depending on res_or_jac (non-AD path only).
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...
FEProblemBase & _fe_problem
virtual void jacobianSetup()
virtual void timestepSetup()
THREAD_ID numThreads() const
auto raw_value(const Eigen::Map< T > &in)
static constexpr std::size_t dim