17#include "libmesh/quadrature.h"
25 "Gravitational acceleration vector downwards (m/s^2)");
27 "PorousFlowDictator",
"The UserObject that holds the list of PorousFlow variable names");
28 params.
addParam<
unsigned>(
"full_upwind_threshold",
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");
37 "quick: use nodal mobility without "
38 "preserving mass. harmonic: use a "
39 "harmonic mean of nodal mobilities "
40 "and preserve fluid mass");
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(
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(
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(
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(
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")),
84 : &this->template getMaterialProperty<
std::vector<
std::vector<RealGradient>>>(
85 "dPorousFlow_grad_porepressure_qp_dvar")),
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")),
93 _proto_flux(_num_phases),
94 _jacobian(_num_phases),
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.");
107#ifdef LIBMESH_HAVE_TBB_API
109 mooseWarning(
"PorousFlowDarcyBase: num_upwinds and num_downwinds may not be computed "
110 "accurately when using TBB and greater than 1 thread");
119 _num_upwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
120 _num_downwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
128 _my_elem_darcy =
nullptr;
135 return _grad_test[_i][_qp] *
136 (_permeability[_qp] * (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity));
143 if constexpr (!is_ad)
145 if (_dictator.notPorousFlowVariable(jvar))
148 const unsigned int pvar = _dictator.porousFlowVariableNum(jvar);
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);
154 deriv += _permeability[_qp] * ((*_dgrad_p_dvar)[_qp][ph][pvar] * _phi[_j][_qp]);
158 deriv += (*_dpermeability_dvar)[_qp][pvar] * _phi[_j][_qp] *
159 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
161 deriv += (*_dpermeability_dgradvar)[_qp][i][pvar] * _grad_phi[_j][_qp](i) *
162 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
165 return _grad_test[_i][_qp] * deriv;
168 libmesh_ignore(jvar, ph);
176 mooseError(
"PorousFlowDarcyBase: computeQpResidual called");
184 if constexpr (!is_ad)
185 computeResidualAndJacobian(JacRes::CALCULATE_RESIDUAL, 0);
188 adComputeProtoFlux(
false);
190 assembleProtoFluxResidual();
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)
202 this->accumulateTaggedLocalResidual();
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());
214 if constexpr (!is_ad)
215 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, _var.number());
236 adComputeProtoFlux(
true);
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];
244 this->addJacobianWithoutConstraints(
245 this->_assembly, darcy_residuals, this->dofIndices(), _var.scalingFactor());
253 if constexpr (!is_ad)
254 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, jvar);
257 libmesh_ignore(jvar);
261 if (_my_elem_darcy != this->_current_elem)
264 _my_elem_darcy = this->_current_elem;
273 const unsigned int num_nodes = _test.size();
274 for (
unsigned ph = 0; ph < _num_phases; ++ph)
276 _proto_flux[ph].assign(num_nodes, 0.0);
277 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
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);
290 if (_num_upwinds.find(elem) == _num_upwinds.end())
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)
296 _num_upwinds[elem][ph].assign(num_nodes, 0);
297 _num_downwinds[elem][ph].assign(num_nodes, 0);
306 for (
unsigned ph = 0; ph < _num_phases; ++ph)
307 for (
unsigned nod = 0; nod < num_nodes; ++nod)
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]++;
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)
324 std::max(max_swaps[ph],
325 std::min(_num_upwinds.at(elem)[ph][nod], _num_downwinds.at(elem)[ph][nod]));
335 for (
unsigned int ph = 0; ph < _num_phases; ++ph)
337 if (max_swaps[ph] < _full_upwind_threshold)
338 fullyUpwind(res_or_jac, ph, pvar);
341 switch (_fallback_scheme)
343 case FallbackEnum::QUICK:
344 quickUpwind(res_or_jac, ph, pvar);
346 case FallbackEnum::HARMONIC:
347 harmonicMean(res_or_jac, ph, pvar);
358 const unsigned int num_nodes = _test.size();
359 computeProtoFluxWithoutMobility();
362 const unsigned elem = this->_current_elem->id();
363 initializeUpwindTracking(elem, num_nodes);
366 updateUpwindCounts(elem, num_nodes);
369 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
372 applyUpwinding(max_swaps, JacRes::CALCULATE_RESIDUAL, 0);
379 if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _dictator.notPorousFlowVariable(jvar))
383 const unsigned int pvar =
384 ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _dictator.porousFlowVariableNum(jvar) : 0);
386 this->prepareMatrixTag(this->_assembly, _var.number(), jvar);
387 if ((this->_local_ke.n() == 0) &&
388 (res_or_jac == JacRes::CALCULATE_JACOBIAN))
395 const unsigned int num_nodes = _test.size();
399 computeProtoFluxWithoutMobility();
402 const unsigned elem = this->_current_elem->id();
403 initializeUpwindTracking(elem, num_nodes);
405 if (res_or_jac == JacRes::CALCULATE_JACOBIAN && jvar == _var.number())
406 updateUpwindCounts(elem, num_nodes);
411 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
414 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
416 for (
unsigned ph = 0; ph < _num_phases; ++ph)
418 _jacobian[ph].resize(this->_local_ke.m());
419 for (_i = 0; _i < _test.size(); _i++)
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);
433 applyUpwinding(max_swaps, res_or_jac, pvar);
436 if (res_or_jac == JacRes::CALCULATE_RESIDUAL)
437 assembleProtoFluxResidual();
439 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
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];
446 this->accumulateTaggedLocalMatrix();
448 if (this->_has_diag_save_in && jvar == _var.number())
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);
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());
468 libmesh_ignore(res_or_jac, pvar);
500 const unsigned int num_nodes = _test.size();
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)
513 dtotal_mass_out.assign(num_nodes, 0.0);
514 dtotal_in.assign(num_nodes, 0.0);
518 std::vector<bool> upwind_node(num_nodes);
519 for (
unsigned int n = 0; n < num_nodes; ++n)
521 if (_proto_flux[ph][n] >= 0.0)
523 upwind_node[n] =
true;
525 mob = mobility(n, ph);
526 if constexpr (!is_ad)
527 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
530 const Real dmob = dmobility(n, ph, pvar);
532 for (_j = 0; _j < _phi.size(); _j++)
533 _jacobian[ph][n][_j] *= mob;
535 if (_test.size() == _phi.size())
546 for (_j = 0; _j < _phi.size(); _j++)
547 dtotal_mass_out[_j] += _jacobian[ph][n][_j];
549 _proto_flux[ph][n] *= mob;
550 total_mass_out += _proto_flux[ph][n];
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];
565 for (
unsigned int n = 0; n < num_nodes; ++n)
569 if constexpr (!is_ad)
570 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
571 for (_j = 0; _j < _phi.size(); _j++)
574 _jacobian[ph][n][_j] +=
580 _proto_flux[ph][n] *= total_mass_out / total_in;
591 libmesh_ignore(res_or_jac, pvar);
594 const unsigned int num_nodes = _test.size();
597 for (
unsigned int n = 0; n < num_nodes; ++n)
601 if constexpr (!is_ad)
602 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
605 const Real dmob = dmobility(n, ph, pvar);
607 for (_j = 0; _j < _phi.size(); _j++)
608 _jacobian[ph][n][_j] *= mob;
610 if (_test.size() == _phi.size())
621 _proto_flux[ph][n] *= mob;
631 libmesh_ignore(res_or_jac, pvar);
634 const unsigned int num_nodes = _test.size();
636 std::vector<GenericReal<is_ad>> mob(num_nodes);
637 unsigned num_zero = 0;
639 for (
unsigned n = 0; n < num_nodes; ++n)
641 mob[n] = mobility(n, ph);
647 harmonic_mob += 1.0 / mob[n];
652 harmonic_mob = (1.0 * num_nodes) / harmonic_mob;
655 std::vector<Real> dharmonic_mob(num_nodes, 0.0);
656 if constexpr (!is_ad)
657 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
661 for (
unsigned n = 0; n < num_nodes; ++n)
664 else if (num_zero == 1)
665 for (
unsigned n = 0; n < num_nodes; ++n)
668 dharmonic_mob[n] = num_nodes * dmobility(n, ph, pvar);
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++)
680 if (_test.size() == _phi.size())
684 for (
unsigned n = 0; n < num_nodes; ++n)
685 _proto_flux[ph][n] *= harmonic_mob;
692 return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
699 if constexpr (!is_ad)
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);
707 libmesh_ignore(nodenum, phase, pvar);
void mooseError(Args &&... args)
Moose::GenericType< Real, is_ad > GenericReal
PetscErrorCode PetscOptionItems *PetscErrorCode DM dm
static InputParameters validParams()
const std::string & name() const
void mooseError(Args &&... args) const
void mooseWarning(Args &&... args) const
bool isNodal() const override
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 ¶meters)
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()
static constexpr std::size_t dim