https://mooseframework.inl.gov
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 
19 template <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 
45 template <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 
114 template <bool is_ad>
115 void
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 
123 template <bool is_ad>
124 void
126 {
128  _my_elem_darcy = nullptr;
129 }
130 
131 template <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 
139 template <bool is_ad>
140 Real
141 PorousFlowDarcyBaseTempl<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 
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 
172 template <bool is_ad>
175 {
176  mooseError("PorousFlowDarcyBase: computeQpResidual called");
177  return 0.0;
178 }
179 
180 template <bool is_ad>
181 void
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 
194 template <bool is_ad>
195 void
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 
210 template <bool is_ad>
211 void
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 
221 template <bool is_ad>
222 void
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 
249 template <bool is_ad>
250 void
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 
269 template <bool is_ad>
270 void
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 
286 template <bool is_ad>
287 void
288 PorousFlowDarcyBaseTempl<is_ad>::initializeUpwindTracking(unsigned elem, unsigned int num_nodes)
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 
302 template <bool is_ad>
303 void
304 PorousFlowDarcyBaseTempl<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 
316 template <bool is_ad>
317 std::vector<unsigned>
318 PorousFlowDarcyBaseTempl<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 
329 template <bool is_ad>
330 void
331 PorousFlowDarcyBaseTempl<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 
354 template <bool is_ad>
355 void
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 
375 template <bool is_ad>
376 void
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 
462 template <bool is_ad>
463 void
464 PorousFlowDarcyBaseTempl<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 
502  GenericReal<is_ad> mob;
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 
585 template <bool is_ad>
586 void
587 PorousFlowDarcyBaseTempl<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 
625 template <bool is_ad>
626 void
627 PorousFlowDarcyBaseTempl<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 
688 template <bool is_ad>
690 PorousFlowDarcyBaseTempl<is_ad>::mobility(unsigned nodenum, unsigned phase) const
691 {
692  return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
693 }
694 
695 template <bool is_ad>
696 Real
697 PorousFlowDarcyBaseTempl<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 
711 template class PorousFlowDarcyBaseTempl<false>;
712 template class PorousFlowDarcyBaseTempl<true>;
Moose::GenericType< Real, is_ad > GenericReal
virtual GenericReal< is_ad > darcyQp(unsigned int ph) const
The Darcy part of the flux (this is the non-upwinded part)
unsigned int n_threads()
void applyUpwinding(const std::vector< unsigned > &max_swaps, JacRes res_or_jac, unsigned int pvar)
Apply selected upwinding/fallback scheme for all phases.
MooseVariable & _var
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void mooseError(Args &&... args)
static InputParameters validParams()
void fullyUpwind(JacRes res_or_jac, unsigned int ph, unsigned int pvar)
Calculate the residual or Jacobian using full upwinding.
auto raw_value(const Eigen::Map< T > &in)
PetscErrorCode PetscOptionItems *PetscErrorCode DM dm
static constexpr std::size_t dim
virtual void computeResidualAndJacobian() override
void adComputeProtoFlux(bool do_counting)
For the AD path: fills _proto_flux (per-phase, per-node ADReal) by integrating darcyQp and applying t...
Darcy advective flux.
void addRequiredParam(const std::string &name, const std::string &doc_string)
virtual void computeResidual() override
std::vector< unsigned > computeMaxSwaps(unsigned elem, unsigned int num_nodes) const
Compute per-phase maximum upwind/downwind swap counts for this element.
TensorValue< Real > RealTensorValue
void libmesh_ignore(const Args &...)
const std::string & name() const
void assembleProtoFluxResidual()
Assemble the real-valued residual vector from _proto_flux into the tagged local residual (and apply s...
virtual void timestepSetup()
Real deriv(unsigned n, unsigned alpha, unsigned beta, Real x)
PorousFlowDarcyBaseTempl(const InputParameters &parameters)
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 void computeOffDiagJacobian(unsigned int jvar) override
void initializeUpwindTracking(unsigned elem, unsigned int num_nodes)
Ensure per-element upwind/downwind counters are allocated for this element.
virtual void timestepSetup() override
void computeProtoFluxWithoutMobility()
Build proto fluxes (without mobility weighting) for all phases and nodes.
static InputParameters validParams()
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 ...
bool isNodal() const override
void adComputeJacobian()
For the AD path: performs the proto-flux/upwinding pass and hands the per-node ADReal residuals to ad...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual Real darcyQpJacobian(unsigned int jvar, unsigned int ph) const
Jacobian of the Darcy part of the flux – non-AD path only.
This holds maps between the nonlinear variables used in a PorousFlow simulation and the variable numb...
virtual Real dmobility(unsigned nodenum, unsigned phase, unsigned pvar) const
The derivative of mobility with respect to PorousFlow variable pvar – non-AD path only...
virtual void jacobianSetup() override
void mooseWarning(Args &&... args) const
IntRange< T > make_range(T beg, T end)
virtual GenericReal< is_ad > mobility(unsigned nodenum, unsigned phase) const
The mobility of the fluid.
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
void updateUpwindCounts(unsigned elem, unsigned int num_nodes)
Update upwind/downwind counters from the sign of _proto_flux.
virtual GenericReal< is_ad > computeQpResidual() override
virtual void jacobianSetup()
MooseUnits pow(const MooseUnits &, int)
virtual void computeJacobian() override
FallbackEnum
If full upwinding is failing due to nodes swapping between upwind and downwind in successive nonlinea...