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
52 "dPorousFlow_permeability_qp_dvar")),
53 _dpermeability_dgradvar(
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")),
85 "dPorousFlow_grad_porepressure_qp_dvar")),
87 _num_phases(_dictator.numPhases()),
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");
114 template <
bool is_ad>
119 _num_upwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
120 _num_downwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
123 template <
bool is_ad>
128 _my_elem_darcy =
nullptr;
131 template <
bool is_ad>
135 return _grad_test[_i][_qp] *
136 (_permeability[_qp] * (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity));
139 template <
bool is_ad>
143 if constexpr (!is_ad)
145 if (_dictator.notPorousFlowVariable(jvar))
148 const unsigned int pvar = _dictator.porousFlowVariableNum(jvar);
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;
172 template <
bool is_ad>
176 mooseError(
"PorousFlowDarcyBase: computeQpResidual called");
180 template <
bool is_ad>
184 if constexpr (!is_ad)
185 computeResidualAndJacobian(JacRes::CALCULATE_RESIDUAL, 0);
188 adComputeProtoFlux(
false);
190 assembleProtoFluxResidual();
194 template <
bool is_ad>
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());
210 template <
bool is_ad>
214 if constexpr (!is_ad)
215 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, _var.number());
221 template <
bool is_ad>
236 adComputeProtoFlux(
true);
238 const unsigned int num_nodes = _test.size();
239 std::vector<ADReal> darcy_residuals(num_nodes, 0.0);
242 darcy_residuals[n] += _proto_flux[ph][n];
244 this->addJacobianWithoutConstraints(
245 this->_assembly, darcy_residuals, this->dofIndices(), _var.scalingFactor());
249 template <
bool is_ad>
253 if constexpr (!is_ad)
254 computeResidualAndJacobian(JacRes::CALCULATE_JACOBIAN, jvar);
261 if (_my_elem_darcy != this->_current_elem)
264 _my_elem_darcy = this->_current_elem;
269 template <
bool is_ad>
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);
286 template <
bool is_ad>
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);
302 template <
bool is_ad>
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]++;
316 template <
bool is_ad>
317 std::vector<unsigned>
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]));
329 template <
bool is_ad>
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);
354 template <
bool is_ad>
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);
375 template <
bool is_ad>
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();
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());
462 template <
bool is_ad>
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;
585 template <
bool is_ad>
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;
625 template <
bool is_ad>
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;
688 template <
bool is_ad>
692 return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
695 template <
bool is_ad>
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);
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)
void applyUpwinding(const std::vector< unsigned > &max_swaps, JacRes res_or_jac, unsigned int pvar)
Apply selected upwinding/fallback scheme for all phases.
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.
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...
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 ¶meters)
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 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...