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. 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.");
108#ifdef LIBMESH_HAVE_TBB_API
110 mooseWarning(
"PorousFlowDarcyBase: num_upwinds and num_downwinds may not be computed "
111 "accurately when using TBB and greater than 1 thread");
120 _num_upwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
121 _num_downwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
129 _my_elem_darcy =
nullptr;
136 return _grad_test[_i][_qp] *
137 (_permeability[_qp] * (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity));
144 if constexpr (!is_ad)
146 if (_dictator.notPorousFlowVariable(jvar))
149 const unsigned int pvar = _dictator.porousFlowVariableNum(jvar);
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);
155 deriv += _permeability[_qp] * ((*_dgrad_p_dvar)[_qp][ph][pvar] * _phi[_j][_qp]);
159 deriv += (*_dpermeability_dvar)[_qp][pvar] * _phi[_j][_qp] *
160 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
162 deriv += (*_dpermeability_dgradvar)[_qp][i][pvar] * _grad_phi[_j][_qp](i) *
163 (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
166 return _grad_test[_i][_qp] * deriv;
169 libmesh_ignore(jvar, ph);
177 mooseError(
"PorousFlowDarcyBase: computeQpResidual called");
185 if constexpr (!is_ad)
186 computeResidualAndJacobian(JacRes::CALCULATE_RESIDUAL, 0);
189 adComputeProtoFlux(
false);
191 assembleProtoFluxResidual();
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)
203 this->accumulateTaggedLocalResidual();
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());
215 if constexpr (!is_ad)
216 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, _var.number());
237 adComputeProtoFlux(
true);
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];
245 this->addJacobianWithoutConstraints(
246 this->_assembly, darcy_residuals, this->dofIndices(), _var.scalingFactor());
254 if constexpr (!is_ad)
255 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, jvar);
258 libmesh_ignore(jvar);
262 if (_my_elem_darcy != this->_current_elem)
265 _my_elem_darcy = this->_current_elem;
274 const unsigned int num_nodes = _test.size();
275 for (
unsigned ph = 0; ph < _num_phases; ++ph)
277 _proto_flux[ph].assign(num_nodes, 0.0);
278 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
280 const Real jxw_coord = this->_JxW[_qp] * this->_coord[_qp];
281 for (_i = 0; _i < num_nodes; ++_i)
282 _proto_flux[ph][_i] += jxw_coord * darcyQp(ph);
291 if (_num_upwinds.find(elem) == _num_upwinds.end())
293 _num_upwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
294 _num_downwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
295 for (
unsigned ph = 0; ph < _num_phases; ++ph)
297 _num_upwinds[elem][ph].assign(num_nodes, 0);
298 _num_downwinds[elem][ph].assign(num_nodes, 0);
307 for (
unsigned ph = 0; ph < _num_phases; ++ph)
308 for (
unsigned nod = 0; nod < num_nodes; ++nod)
310 if (_proto_flux[ph][nod] > 0)
311 _num_upwinds[elem][ph][nod]++;
312 else if (_proto_flux[ph][nod] < 0)
313 _num_downwinds[elem][ph][nod]++;
321 std::vector<unsigned> max_swaps(_num_phases, 0);
322 for (
unsigned ph = 0; ph < _num_phases; ++ph)
323 for (
unsigned nod = 0; nod < num_nodes; ++nod)
325 std::max(max_swaps[ph],
326 std::min(_num_upwinds.at(elem)[ph][nod], _num_downwinds.at(elem)[ph][nod]));
336 for (
unsigned int ph = 0; ph < _num_phases; ++ph)
338 if (max_swaps[ph] < _full_upwind_threshold)
339 fullyUpwind(res_or_jac, ph, pvar);
342 switch (_fallback_scheme)
344 case FallbackEnum::QUICK:
345 quickUpwind(res_or_jac, ph, pvar);
347 case FallbackEnum::HARMONIC:
348 harmonicMean(res_or_jac, ph, pvar);
359 const unsigned int num_nodes = _test.size();
360 computeProtoFluxWithoutMobility();
363 const unsigned elem = this->_current_elem->id();
364 initializeUpwindTracking(elem, num_nodes);
367 updateUpwindCounts(elem, num_nodes);
370 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
373 applyUpwinding(max_swaps, JacRes::CALCULATE_RESIDUAL, 0);
380 if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _dictator.notPorousFlowVariable(jvar))
384 const unsigned int pvar =
385 ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _dictator.porousFlowVariableNum(jvar) : 0);
387 this->prepareMatrixTag(this->_assembly, _var.number(), jvar);
388 if ((this->_local_ke.n() == 0) &&
389 (res_or_jac == JacRes::CALCULATE_JACOBIAN))
396 const unsigned int num_nodes = _test.size();
400 computeProtoFluxWithoutMobility();
403 const unsigned elem = this->_current_elem->id();
404 initializeUpwindTracking(elem, num_nodes);
406 if (res_or_jac == JacRes::CALCULATE_JACOBIAN && jvar == _var.number())
407 updateUpwindCounts(elem, num_nodes);
412 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
415 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
417 for (
unsigned ph = 0; ph < _num_phases; ++ph)
419 _jacobian[ph].resize(this->_local_ke.m());
420 for (_i = 0; _i < _test.size(); _i++)
422 _jacobian[ph][_i].assign(this->_local_ke.n(), 0.0);
423 for (_j = 0; _j < _phi.size(); _j++)
424 for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
425 _jacobian[ph][_i][_j] +=
426 this->_JxW[_qp] * this->_coord[_qp] * darcyQpJacobian(jvar, ph);
434 applyUpwinding(max_swaps, res_or_jac, pvar);
437 if (res_or_jac == JacRes::CALCULATE_RESIDUAL)
438 assembleProtoFluxResidual();
440 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
442 for (_i = 0; _i < _test.size(); _i++)
443 for (_j = 0; _j < _phi.size(); _j++)
444 for (
unsigned int ph = 0; ph < _num_phases; ++ph)
445 this->_local_ke(_i, _j) += _jacobian[ph][_i][_j];
447 this->accumulateTaggedLocalMatrix();
449 if (this->_has_diag_save_in && jvar == _var.number())
451 unsigned int rows = this->_local_ke.m();
452 DenseVector<Number> diag(rows);
453 for (
unsigned int i = 0; i < rows; i++)
454 diag(i) = this->_local_ke(i, i);
456 for (
unsigned int i = 0; i < this->_diag_save_in.size(); i++)
457 this->_diag_save_in[i]->sys().solution().add_vector(diag,
458 this->_diag_save_in[i]->dofIndices());
469 libmesh_ignore(res_or_jac, pvar);
501 const unsigned int num_nodes = _test.size();
509 std::vector<Real> dtotal_mass_out;
510 std::vector<Real> dtotal_in;
511 if constexpr (!is_ad)
512 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
514 dtotal_mass_out.assign(num_nodes, 0.0);
515 dtotal_in.assign(num_nodes, 0.0);
519 std::vector<bool> upwind_node(num_nodes);
520 for (
unsigned int n = 0; n < num_nodes; ++n)
522 if (_proto_flux[ph][n] >= 0.0)
524 upwind_node[n] =
true;
526 mob = mobility(n, ph);
527 if constexpr (!is_ad)
528 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
531 const Real dmob = dmobility(n, ph, pvar);
533 for (_j = 0; _j < _phi.size(); _j++)
534 _jacobian[ph][n][_j] *= mob;
536 if (_test.size() == _phi.size())
547 for (_j = 0; _j < _phi.size(); _j++)
548 dtotal_mass_out[_j] += _jacobian[ph][n][_j];
550 _proto_flux[ph][n] *= mob;
551 total_mass_out += _proto_flux[ph][n];
555 upwind_node[n] =
false;
556 total_in -= _proto_flux[ph][n];
557 if constexpr (!is_ad)
558 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
559 for (_j = 0; _j < _phi.size(); _j++)
560 dtotal_in[_j] -= _jacobian[ph][n][_j];
566 for (
unsigned int n = 0; n < num_nodes; ++n)
570 if constexpr (!is_ad)
571 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
572 for (_j = 0; _j < _phi.size(); _j++)
575 _jacobian[ph][n][_j] +=
581 _proto_flux[ph][n] *= total_mass_out / total_in;
592 libmesh_ignore(res_or_jac, pvar);
595 const unsigned int num_nodes = _test.size();
598 for (
unsigned int n = 0; n < num_nodes; ++n)
602 if constexpr (!is_ad)
603 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
606 const Real dmob = dmobility(n, ph, pvar);
608 for (_j = 0; _j < _phi.size(); _j++)
609 _jacobian[ph][n][_j] *= mob;
611 if (_test.size() == _phi.size())
622 _proto_flux[ph][n] *= mob;
632 libmesh_ignore(res_or_jac, pvar);
635 const unsigned int num_nodes = _test.size();
637 std::vector<GenericReal<is_ad>> mob(num_nodes);
638 unsigned num_zero = 0;
640 for (
unsigned n = 0; n < num_nodes; ++n)
642 mob[n] = mobility(n, ph);
648 harmonic_mob += 1.0 / mob[n];
653 harmonic_mob = (1.0 * num_nodes) / harmonic_mob;
656 std::vector<Real> dharmonic_mob(num_nodes, 0.0);
657 if constexpr (!is_ad)
658 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
662 for (
unsigned n = 0; n < num_nodes; ++n)
665 else if (num_zero == 1)
666 for (
unsigned n = 0; n < num_nodes; ++n)
669 dharmonic_mob[n] = num_nodes * dmobility(n, ph, pvar);
675 if constexpr (!is_ad)
676 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
677 for (
unsigned n = 0; n < num_nodes; ++n)
678 for (_j = 0; _j < _phi.size(); _j++)
681 if (_test.size() == _phi.size())
685 for (
unsigned n = 0; n < num_nodes; ++n)
686 _proto_flux[ph][n] *= harmonic_mob;
693 return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
700 if constexpr (!is_ad)
702 Real
dm = (*_dfluid_density_node_dvar)[nodenum][phase][pvar] / _fluid_viscosity[nodenum][phase];
703 dm -= _fluid_density_node[nodenum][phase] * (*_dfluid_viscosity_dvar)[nodenum][phase][pvar] /
704 std::pow(_fluid_viscosity[nodenum][phase], 2);
708 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