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 computeResidualOrJacobian(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 computeResidualOrJacobian(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 computeResidualOrJacobian(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++)
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);
296 if (_num_upwinds.find(elem) == _num_upwinds.end())
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)
302 _num_upwinds[elem][ph].assign(num_nodes, 0);
303 _num_downwinds[elem][ph].assign(num_nodes, 0);
312 for (
unsigned ph = 0; ph < _num_phases; ++ph)
313 for (
unsigned nod = 0; nod < num_nodes; ++nod)
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]++;
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)
330 std::max(max_swaps[ph],
331 std::min(_num_upwinds.at(elem)[ph][nod], _num_downwinds.at(elem)[ph][nod]));
341 for (
unsigned int ph = 0; ph < _num_phases; ++ph)
343 if (max_swaps[ph] < _full_upwind_threshold)
344 fullyUpwind(res_or_jac, ph, pvar);
347 switch (_fallback_scheme)
349 case FallbackEnum::QUICK:
350 quickUpwind(res_or_jac, ph, pvar);
352 case FallbackEnum::HARMONIC:
353 harmonicMean(res_or_jac, ph, pvar);
364 const unsigned int num_nodes = _test.size();
365 computeProtoFluxWithoutMobility();
368 const unsigned elem = this->_current_elem->id();
369 initializeUpwindTracking(elem, num_nodes);
372 updateUpwindCounts(elem, num_nodes);
375 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
378 applyUpwinding(max_swaps, JacRes::CALCULATE_RESIDUAL, 0);
385 if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _dictator.notPorousFlowVariable(jvar))
389 const unsigned int pvar =
390 ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _dictator.porousFlowVariableNum(jvar) : 0);
392 this->prepareMatrixTag(this->_assembly, _var.number(), jvar);
393 if ((this->_local_ke.n() == 0) &&
394 (res_or_jac == JacRes::CALCULATE_JACOBIAN))
401 const unsigned int num_nodes = _test.size();
405 computeProtoFluxWithoutMobility();
408 const unsigned elem = this->_current_elem->id();
409 initializeUpwindTracking(elem, num_nodes);
411 if (res_or_jac == JacRes::CALCULATE_JACOBIAN && jvar == _var.number())
412 updateUpwindCounts(elem, num_nodes);
417 const std::vector<unsigned> max_swaps = computeMaxSwaps(elem, num_nodes);
420 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
422 for (
unsigned ph = 0; ph < _num_phases; ++ph)
424 _jacobian[ph].resize(this->_local_ke.m());
425 for (_i = 0; _i < _test.size(); _i++)
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);
439 applyUpwinding(max_swaps, res_or_jac, pvar);
442 if (res_or_jac == JacRes::CALCULATE_RESIDUAL)
443 assembleProtoFluxResidual();
445 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
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];
452 this->accumulateTaggedLocalMatrix();
454 if (this->_has_diag_save_in && jvar == _var.number())
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);
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());
474 libmesh_ignore(res_or_jac, pvar);
506 const unsigned int num_nodes = _test.size();
514 std::vector<Real> dtotal_mass_out;
515 std::vector<Real> dtotal_in;
519 if constexpr (!is_ad)
520 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
522 dtotal_mass_out.assign(_phi.size(), 0.0);
523 dtotal_in.assign(_phi.size(), 0.0);
527 std::vector<bool> upwind_node(num_nodes);
528 for (
unsigned int n = 0; n < num_nodes; ++n)
530 if (_proto_flux[ph][n] >= 0.0)
532 upwind_node[n] =
true;
534 mob = mobility(n, ph);
535 if constexpr (!is_ad)
536 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
539 const Real dmob = dmobility(n, ph, pvar);
541 for (_j = 0; _j < _phi.size(); _j++)
542 _jacobian[ph][n][_j] *= mob;
544 if (_test.size() == _phi.size())
555 for (_j = 0; _j < _phi.size(); _j++)
556 dtotal_mass_out[_j] += _jacobian[ph][n][_j];
558 _proto_flux[ph][n] *= mob;
559 total_mass_out += _proto_flux[ph][n];
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];
574 for (
unsigned int n = 0; n < num_nodes; ++n)
578 if constexpr (!is_ad)
579 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
580 for (_j = 0; _j < _phi.size(); _j++)
583 _jacobian[ph][n][_j] +=
589 _proto_flux[ph][n] *= total_mass_out / total_in;
600 libmesh_ignore(res_or_jac, pvar);
603 const unsigned int num_nodes = _test.size();
606 for (
unsigned int n = 0; n < num_nodes; ++n)
610 if constexpr (!is_ad)
611 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
614 const Real dmob = dmobility(n, ph, pvar);
616 for (_j = 0; _j < _phi.size(); _j++)
617 _jacobian[ph][n][_j] *= mob;
619 if (_test.size() == _phi.size())
630 _proto_flux[ph][n] *= mob;
640 libmesh_ignore(res_or_jac, pvar);
643 const unsigned int num_nodes = _test.size();
645 std::vector<GenericReal<is_ad>> mob(num_nodes);
646 unsigned num_zero = 0;
648 for (
unsigned n = 0; n < num_nodes; ++n)
650 mob[n] = mobility(n, ph);
656 harmonic_mob += 1.0 / mob[n];
661 harmonic_mob = (1.0 * num_nodes) / harmonic_mob;
664 std::vector<Real> dharmonic_mob(num_nodes, 0.0);
665 if constexpr (!is_ad)
666 if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
670 for (
unsigned n = 0; n < num_nodes; ++n)
673 else if (num_zero == 1)
674 for (
unsigned n = 0; n < num_nodes; ++n)
677 dharmonic_mob[n] = num_nodes * dmobility(n, ph, pvar);
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++)
689 if (_test.size() == _phi.size())
693 for (
unsigned n = 0; n < num_nodes; ++n)
694 _proto_flux[ph][n] *= harmonic_mob;
701 return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
708 if constexpr (!is_ad)
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);
716 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 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 ¶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...
FEProblemBase & _fe_problem
virtual void jacobianSetup()
virtual void timestepSetup()
THREAD_ID numThreads() const
static constexpr std::size_t dim