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. For non-nodal variables use the non-upwinded "
105 "PorousFlowFullySaturated* kernels or Kuzmin-Turek (KT) stabilisation instead.");
106
107#ifdef LIBMESH_HAVE_TBB_API
108 if (libMesh::n_threads() > 1)
109 mooseWarning("PorousFlowDarcyBase: num_upwinds and num_downwinds may not be computed "
110 "accurately when using TBB and greater than 1 thread");
111#endif
112}
113
114template <bool is_ad>
115void
117{
119 _num_upwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
120 _num_downwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
121}
122
123template <bool is_ad>
124void
130
131template <bool is_ad>
134{
135 return _grad_test[_i][_qp] *
136 (_permeability[_qp] * (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity));
137}
138
139template <bool is_ad>
140Real
141PorousFlowDarcyBaseTempl<is_ad>::darcyQpJacobian(unsigned int jvar, unsigned int ph) const
142{
143 if constexpr (!is_ad)
144 {
145 if (_dictator.notPorousFlowVariable(jvar))
146 return 0.0;
147
148 const unsigned int pvar = _dictator.porousFlowVariableNum(jvar);
149
150 RealVectorValue deriv =
151 _permeability[_qp] * (_grad_phi[_j][_qp] * (*_dgrad_p_dgrad_var)[_qp][ph][pvar] -
152 _phi[_j][_qp] * (*_dfluid_density_qp_dvar)[_qp][ph][pvar] * _gravity);
153
154 deriv += _permeability[_qp] * ((*_dgrad_p_dvar)[_qp][ph][pvar] * _phi[_j][_qp]);
155
156 if (_perm_derivs)
157 {
158 deriv += (*_dpermeability_dvar)[_qp][pvar] * _phi[_j][_qp] *
159 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
160 for (const auto i : make_range(Moose::dim))
161 deriv += (*_dpermeability_dgradvar)[_qp][i][pvar] * _grad_phi[_j][_qp](i) *
162 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
163 }
164
165 return _grad_test[_i][_qp] * deriv;
166 }
167 else
168 libmesh_ignore(jvar, ph);
169 return 0.0;
170}
171
172template <bool is_ad>
175{
176 mooseError("PorousFlowDarcyBase: computeQpResidual called");
177 return 0.0;
178}
179
180template <bool is_ad>
181void
183{
184 if constexpr (!is_ad)
185 computeResidualAndJacobian(JacRes::CALCULATE_RESIDUAL, 0);
186 else
187 {
188 adComputeProtoFlux(false);
189 // Assemble the real-valued residual from the value parts of the ADReal proto fluxes
190 assembleProtoFluxResidual();
191 }
192}
193
194template <bool is_ad>
195void
197{
198 this->prepareVectorTag(this->_assembly, _var.number());
199 for (_i = 0; _i < _test.size(); _i++)
200 for (unsigned int ph = 0; ph < _num_phases; ++ph)
201 this->_local_re(_i) += MetaPhysicL::raw_value(_proto_flux[ph][_i]);
202 this->accumulateTaggedLocalResidual();
203
204 if (this->_has_save_in)
205 for (unsigned int i = 0; i < this->_save_in.size(); i++)
206 this->_save_in[i]->sys().solution().add_vector(this->_local_re,
207 this->_save_in[i]->dofIndices());
208}
209
210template <bool is_ad>
211void
213{
214 if constexpr (!is_ad)
215 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, _var.number());
216 else
217 // Block-diagonal (e.g. PJFNK) assembly path: this is the only Jacobian call we get.
218 adComputeJacobian();
219}
220
221template <bool is_ad>
222void
224{
225 if constexpr (is_ad)
226 {
227 // Compute ADReal proto fluxes with upwind counting, then extract the Jacobian.
228 // Every block (diagonal and off-diagonal) is encoded in the ADReal derivatives, so a
229 // single call to addJacobianWithoutConstraints captures all variable sensitivities.
230 //
231 // See PorousFlowLumpedKernelBase.C::computeJacobian for the detailed rationale of why
232 // addJacobianWithoutConstraints (rather than the default ADKernel addJacobian) is required:
233 // each residual row's column set must be read from its own derivatives instead of being shared
234 // from row 0. There it is forced by mass-lumped nodal materials; here it is forced by the
235 // upwinding scheme, which makes each node's proto flux depend on a different set of nodal DOFs.
236 adComputeProtoFlux(true);
237
238 const unsigned int num_nodes = _test.size();
239 std::vector<ADReal> darcy_residuals(num_nodes, 0.0);
240 for (const auto n : make_range(num_nodes))
241 for (const auto ph : make_range(_num_phases))
242 darcy_residuals[n] += _proto_flux[ph][n];
243
244 this->addJacobianWithoutConstraints(
245 this->_assembly, darcy_residuals, this->dofIndices(), _var.scalingFactor());
246 }
247}
248
249template <bool is_ad>
250void
252{
253 if constexpr (!is_ad)
254 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, jvar);
255 else
256 {
257 libmesh_ignore(jvar);
258 // Full (SMP) assembly calls this once per coupled variable; the first call performs the
259 // single AD pass that fills every block, and the guard makes the rest no-ops. This mirrors
260 // ADKernel::computeOffDiagJacobian and avoids the ADD-semantics double-counting.
261 if (_my_elem_darcy != this->_current_elem)
262 {
263 adComputeJacobian();
264 _my_elem_darcy = this->_current_elem;
265 }
266 }
267}
268
269template <bool is_ad>
270void
272{
273 const unsigned int num_nodes = _test.size();
274 for (unsigned ph = 0; ph < _num_phases; ++ph)
275 {
276 _proto_flux[ph].assign(num_nodes, 0.0);
277 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
278 {
279 const Real jxw_coord = this->_JxW[_qp] * this->_coord[_qp];
280 for (_i = 0; _i < num_nodes; ++_i)
281 _proto_flux[ph][_i] += jxw_coord * darcyQp(ph);
282 }
283 }
284}
285
286template <bool is_ad>
287void
289{
290 if (_num_upwinds.find(elem) == _num_upwinds.end())
291 {
292 _num_upwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
293 _num_downwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
294 for (unsigned ph = 0; ph < _num_phases; ++ph)
295 {
296 _num_upwinds[elem][ph].assign(num_nodes, 0);
297 _num_downwinds[elem][ph].assign(num_nodes, 0);
298 }
299 }
300}
301
302template <bool is_ad>
303void
304PorousFlowDarcyBaseTempl<is_ad>::updateUpwindCounts(unsigned elem, unsigned int num_nodes)
305{
306 for (unsigned ph = 0; ph < _num_phases; ++ph)
307 for (unsigned nod = 0; nod < num_nodes; ++nod)
308 {
309 if (_proto_flux[ph][nod] > 0)
310 _num_upwinds[elem][ph][nod]++;
311 else if (_proto_flux[ph][nod] < 0)
312 _num_downwinds[elem][ph][nod]++;
313 }
314}
315
316template <bool is_ad>
317std::vector<unsigned>
318PorousFlowDarcyBaseTempl<is_ad>::computeMaxSwaps(unsigned elem, unsigned int num_nodes) const
319{
320 std::vector<unsigned> max_swaps(_num_phases, 0);
321 for (unsigned ph = 0; ph < _num_phases; ++ph)
322 for (unsigned nod = 0; nod < num_nodes; ++nod)
323 max_swaps[ph] =
324 std::max(max_swaps[ph],
325 std::min(_num_upwinds.at(elem)[ph][nod], _num_downwinds.at(elem)[ph][nod]));
326 return max_swaps;
327}
328
329template <bool is_ad>
330void
331PorousFlowDarcyBaseTempl<is_ad>::applyUpwinding(const std::vector<unsigned> & max_swaps,
332 JacRes res_or_jac,
333 unsigned int pvar)
334{
335 for (unsigned int ph = 0; ph < _num_phases; ++ph)
336 {
337 if (max_swaps[ph] < _full_upwind_threshold)
338 fullyUpwind(res_or_jac, ph, pvar);
339 else
340 {
341 switch (_fallback_scheme)
342 {
343 case FallbackEnum::QUICK:
344 quickUpwind(res_or_jac, ph, pvar);
345 break;
346 case FallbackEnum::HARMONIC:
347 harmonicMean(res_or_jac, ph, pvar);
348 break;
349 }
350 }
351 }
352}
353
354template <bool is_ad>
355void
357{
358 const unsigned int num_nodes = _test.size();
359 computeProtoFluxWithoutMobility();
360
361 // Initialise upwind-tracking maps for this element on first encounter
362 const unsigned elem = this->_current_elem->id();
363 initializeUpwindTracking(elem, num_nodes);
364
365 if (do_counting)
366 updateUpwindCounts(elem, num_nodes);
367
368 // Determine how many upwind-downwind swaps have occurred this timestep
369 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
370
371 // Apply mobility via the chosen upwinding scheme (always in residual mode for AD)
372 applyUpwinding(max_swaps, JacRes::CALCULATE_RESIDUAL, 0);
373}
374
375template <bool is_ad>
376void
378{
379 if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _dictator.notPorousFlowVariable(jvar))
380 return;
381
382 // The PorousFlow variable index corresponding to the variable number jvar
383 const unsigned int pvar =
384 ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _dictator.porousFlowVariableNum(jvar) : 0);
385
386 this->prepareMatrixTag(this->_assembly, _var.number(), jvar);
387 if ((this->_local_ke.n() == 0) &&
388 (res_or_jac == JacRes::CALCULATE_JACOBIAN)) // this removes a problem
389 // encountered in the
390 // initial timestep when
391 // use_displaced_mesh=true
392 return;
393
394 // The number of nodes in the element
395 const unsigned int num_nodes = _test.size();
396
397 // Compute the residual and jacobian without the mobility terms. Even if we are computing the
398 // Jacobian we still need this in order to see which nodes are upwind and which are downwind.
399 computeProtoFluxWithoutMobility();
400
401 // for this element, record whether each node is "upwind" or "downwind" (or neither)
402 const unsigned elem = this->_current_elem->id();
403 initializeUpwindTracking(elem, num_nodes);
404 // record the information once per nonlinear iteration
405 if (res_or_jac == JacRes::CALCULATE_JACOBIAN && jvar == _var.number())
406 updateUpwindCounts(elem, num_nodes);
407
408 // based on _num_upwinds and _num_downwinds, calculate the maximum number
409 // of upwind-downwind swaps that have been encountered in this timestep
410 // for this element
411 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
412
413 // size the _jacobian correctly and calculate it for the case residual = _proto_flux
414 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
415 {
416 for (unsigned ph = 0; ph < _num_phases; ++ph)
417 {
418 _jacobian[ph].resize(this->_local_ke.m());
419 for (_i = 0; _i < _test.size(); _i++)
420 {
421 _jacobian[ph][_i].assign(this->_local_ke.n(), 0.0);
422 for (_j = 0; _j < _phi.size(); _j++)
423 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
424 _jacobian[ph][_i][_j] +=
425 this->_JxW[_qp] * this->_coord[_qp] * darcyQpJacobian(jvar, ph);
426 }
427 }
428 }
429
430 // Loop over all the phases, computing the mass flux, which
431 // gets placed into _proto_flux, and the derivative of this
432 // which gets placed into _jacobian
433 applyUpwinding(max_swaps, res_or_jac, pvar);
434
435 // Add results to the Residual or Jacobian
436 if (res_or_jac == JacRes::CALCULATE_RESIDUAL)
437 assembleProtoFluxResidual();
438
439 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
440 {
441 for (_i = 0; _i < _test.size(); _i++)
442 for (_j = 0; _j < _phi.size(); _j++)
443 for (unsigned int ph = 0; ph < _num_phases; ++ph)
444 this->_local_ke(_i, _j) += _jacobian[ph][_i][_j];
445
446 this->accumulateTaggedLocalMatrix();
447
448 if (this->_has_diag_save_in && jvar == _var.number())
449 {
450 unsigned int rows = this->_local_ke.m();
451 DenseVector<Number> diag(rows);
452 for (unsigned int i = 0; i < rows; i++)
453 diag(i) = this->_local_ke(i, i);
454
455 for (unsigned int i = 0; i < this->_diag_save_in.size(); i++)
456 this->_diag_save_in[i]->sys().solution().add_vector(diag,
457 this->_diag_save_in[i]->dofIndices());
458 }
459 }
460}
461
462template <bool is_ad>
463void
464PorousFlowDarcyBaseTempl<is_ad>::fullyUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
465{
466 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
467 if constexpr (is_ad)
468 libmesh_ignore(res_or_jac, pvar);
469
499 // The number of nodes in the element
500 const unsigned int num_nodes = _test.size();
501
503 // Define variables used to ensure mass conservation
504 GenericReal<is_ad> total_mass_out = 0.0;
505 GenericReal<is_ad> total_in = 0.0;
506
507 // The following holds derivatives of these (non-AD path only)
508 std::vector<Real> dtotal_mass_out;
509 std::vector<Real> dtotal_in;
510 if constexpr (!is_ad)
511 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
512 {
513 dtotal_mass_out.assign(num_nodes, 0.0);
514 dtotal_in.assign(num_nodes, 0.0);
515 }
516
517 // Perform the upwinding using the mobility
518 std::vector<bool> upwind_node(num_nodes);
519 for (unsigned int n = 0; n < num_nodes; ++n)
520 {
521 if (_proto_flux[ph][n] >= 0.0) // upstream node
522 {
523 upwind_node[n] = true;
524 // The mobility at the upstream node
525 mob = mobility(n, ph);
526 if constexpr (!is_ad)
527 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
528 {
529 // The derivative of the mobility wrt the PorousFlow variable
530 const Real dmob = dmobility(n, ph, pvar);
531
532 for (_j = 0; _j < _phi.size(); _j++)
533 _jacobian[ph][n][_j] *= mob;
534
535 if (_test.size() == _phi.size())
536 /* mobility at node=n depends only on the variables at node=n, by construction. For
537 * linear-lagrange variables, this means that Jacobian entries involving the derivative
538 * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the
539 * [n][n] in the line below. However, for other variable types (eg constant monomials)
540 * I cannot tell what variable number contributes to the derivative. However, in all
541 * cases I can possibly imagine, the derivative is zero anyway, since in the full
542 * upwinding scheme, mobility shouldn't depend on these other sorts of variables.
543 */
544 _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
545
546 for (_j = 0; _j < _phi.size(); _j++)
547 dtotal_mass_out[_j] += _jacobian[ph][n][_j];
548 }
549 _proto_flux[ph][n] *= mob;
550 total_mass_out += _proto_flux[ph][n];
551 }
552 else
553 {
554 upwind_node[n] = false;
555 total_in -= _proto_flux[ph][n];
556 if constexpr (!is_ad)
557 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
558 for (_j = 0; _j < _phi.size(); _j++)
559 dtotal_in[_j] -= _jacobian[ph][n][_j];
560 }
561 }
562
563 // Conserve mass over all phases by proportioning the total_mass_out mass to the inflow nodes,
564 // weighted by their proto_flux values
565 for (unsigned int n = 0; n < num_nodes; ++n)
566 {
567 if (!upwind_node[n]) // downstream node
568 {
569 if constexpr (!is_ad)
570 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
571 for (_j = 0; _j < _phi.size(); _j++)
572 {
573 _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(total_mass_out / total_in);
574 _jacobian[ph][n][_j] +=
575 MetaPhysicL::raw_value(_proto_flux[ph][n]) *
576 (dtotal_mass_out[_j] / MetaPhysicL::raw_value(total_in) -
577 dtotal_in[_j] * MetaPhysicL::raw_value(total_mass_out) /
578 MetaPhysicL::raw_value(total_in) / MetaPhysicL::raw_value(total_in));
579 }
580 _proto_flux[ph][n] *= total_mass_out / total_in;
581 }
582 }
583}
584
585template <bool is_ad>
586void
587PorousFlowDarcyBaseTempl<is_ad>::quickUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
588{
589 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
590 if constexpr (is_ad)
591 libmesh_ignore(res_or_jac, pvar);
592
593 // The number of nodes in the element
594 const unsigned int num_nodes = _test.size();
595
596 // Use the raw nodal mobility
597 for (unsigned int n = 0; n < num_nodes; ++n)
598 {
599 // The mobility at the node
600 const GenericReal<is_ad> mob = mobility(n, ph);
601 if constexpr (!is_ad)
602 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
603 {
604 // The derivative of the mobility wrt the PorousFlow variable
605 const Real dmob = dmobility(n, ph, pvar);
606
607 for (_j = 0; _j < _phi.size(); _j++)
608 _jacobian[ph][n][_j] *= mob;
609
610 if (_test.size() == _phi.size())
611 /* mobility at node=n depends only on the variables at node=n, by construction. For
612 * linear-lagrange variables, this means that Jacobian entries involving the derivative
613 * of mobility will only be nonzero for derivatives wrt variables at node=n. Hence the
614 * [n][n] in the line below. However, for other variable types (eg constant monomials)
615 * I cannot tell what variable number contributes to the derivative. However, in all
616 * cases I can possibly imagine, the derivative is zero anyway, since in the full
617 * upwinding scheme, mobility shouldn't depend on these other sorts of variables.
618 */
619 _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
620 }
621 _proto_flux[ph][n] *= mob;
622 }
623}
624
625template <bool is_ad>
626void
627PorousFlowDarcyBaseTempl<is_ad>::harmonicMean(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
628{
629 // res_or_jac and pvar drive the hand-coded non-AD Jacobian only; the AD path ignores them
630 if constexpr (is_ad)
631 libmesh_ignore(res_or_jac, pvar);
632
633 // The number of nodes in the element
634 const unsigned int num_nodes = _test.size();
635
636 std::vector<GenericReal<is_ad>> mob(num_nodes);
637 unsigned num_zero = 0;
638 GenericReal<is_ad> harmonic_mob = 0;
639 for (unsigned n = 0; n < num_nodes; ++n)
640 {
641 mob[n] = mobility(n, ph);
642 if (MetaPhysicL::raw_value(mob[n]) == 0.0)
643 {
644 num_zero++;
645 }
646 else
647 harmonic_mob += 1.0 / mob[n];
648 }
649 if (num_zero > 0)
650 harmonic_mob = 0.0;
651 else
652 harmonic_mob = (1.0 * num_nodes) / harmonic_mob;
653
654 // d(harmonic_mob)/d(PorousFlow variable at node n) -- non-AD path only
655 std::vector<Real> dharmonic_mob(num_nodes, 0.0);
656 if constexpr (!is_ad)
657 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
658 {
659 const Real harm2 = std::pow(MetaPhysicL::raw_value(harmonic_mob), 2) / (1.0 * num_nodes);
660 if (num_zero == 0)
661 for (unsigned n = 0; n < num_nodes; ++n)
662 dharmonic_mob[n] =
663 dmobility(n, ph, pvar) * harm2 / std::pow(MetaPhysicL::raw_value(mob[n]), 2);
664 else if (num_zero == 1)
665 for (unsigned n = 0; n < num_nodes; ++n)
666 if (MetaPhysicL::raw_value(mob[n]) == 0.0)
667 {
668 dharmonic_mob[n] = num_nodes * dmobility(n, ph, pvar); // other derivs are zero
669 break;
670 }
671 // if num_zero > 1 then all dharmonic_mob = 0.0
672 }
673
674 if constexpr (!is_ad)
675 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
676 for (unsigned n = 0; n < num_nodes; ++n)
677 for (_j = 0; _j < _phi.size(); _j++)
678 {
679 _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(harmonic_mob);
680 if (_test.size() == _phi.size())
681 _jacobian[ph][n][_j] += dharmonic_mob[_j] * MetaPhysicL::raw_value(_proto_flux[ph][n]);
682 }
683
684 for (unsigned n = 0; n < num_nodes; ++n)
685 _proto_flux[ph][n] *= harmonic_mob;
686}
687
688template <bool is_ad>
690PorousFlowDarcyBaseTempl<is_ad>::mobility(unsigned nodenum, unsigned phase) const
691{
692 return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
693}
694
695template <bool is_ad>
696Real
697PorousFlowDarcyBaseTempl<is_ad>::dmobility(unsigned nodenum, unsigned phase, unsigned pvar) const
698{
699 if constexpr (!is_ad)
700 {
701 Real dm = (*_dfluid_density_node_dvar)[nodenum][phase][pvar] / _fluid_viscosity[nodenum][phase];
702 dm -= _fluid_density_node[nodenum][phase] * (*_dfluid_viscosity_dvar)[nodenum][phase][pvar] /
703 std::pow(_fluid_viscosity[nodenum][phase], 2);
704 return dm;
705 }
706 else
707 libmesh_ignore(nodenum, phase, pvar);
708 return 0.0;
709}
710
712template 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()