LCOV - code coverage report
Current view: top level - src/kernels - PorousFlowDarcyBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose porous_flow: #33380 (547b29) with base 8581c3 Lines: 263 292 90.1 %
Date: 2026-07-20 19:40:36 Functions: 35 48 72.9 %
Legend: Lines: hit not hit

          Line data    Source code
       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>
      20             : InputParameters
      21        4833 : PorousFlowDarcyBaseTempl<is_ad>::validParams()
      22             : {
      23             :   InputParameters params = GenericKernel<is_ad>::validParams();
      24        9666 :   params.addRequiredParam<RealVectorValue>("gravity",
      25             :                                            "Gravitational acceleration vector downwards (m/s^2)");
      26        9666 :   params.addRequiredParam<UserObjectName>(
      27             :       "PorousFlowDictator", "The UserObject that holds the list of PorousFlow variable names");
      28        9666 :   params.addParam<unsigned>("full_upwind_threshold",
      29        9666 :                             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        9666 :   MooseEnum fallback_enum("quick harmonic", "quick");
      35        9666 :   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        4833 :   params.addClassDescription("Fully-upwinded advective Darcy flux");
      42        4833 :   return params;
      43        4833 : }
      44             : 
      45             : template <bool is_ad>
      46        2602 : PorousFlowDarcyBaseTempl<is_ad>::PorousFlowDarcyBaseTempl(const InputParameters & parameters)
      47             :   : GenericKernel<is_ad>(parameters),
      48        2602 :     _permeability(this->template getGenericMaterialProperty<RealTensorValue, is_ad>(
      49             :         "PorousFlow_permeability_qp")),
      50        2602 :     _dpermeability_dvar(is_ad ? nullptr
      51        2539 :                               : &this->template getMaterialProperty<std::vector<RealTensorValue>>(
      52             :                                     "dPorousFlow_permeability_qp_dvar")),
      53        2602 :     _dpermeability_dgradvar(
      54             :         is_ad ? nullptr
      55        2539 :               : &this->template getMaterialProperty<std::vector<std::vector<RealTensorValue>>>(
      56             :                     "dPorousFlow_permeability_qp_dgradvar")),
      57        5204 :     _fluid_density_node(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
      58             :         "PorousFlow_fluid_phase_density_nodal")),
      59        2602 :     _dfluid_density_node_dvar(
      60             :         is_ad ? nullptr
      61        2539 :               : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
      62             :                     "dPorousFlow_fluid_phase_density_nodal_dvar")),
      63        5204 :     _fluid_density_qp(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
      64             :         "PorousFlow_fluid_phase_density_qp")),
      65        2602 :     _dfluid_density_qp_dvar(
      66             :         is_ad ? nullptr
      67        2539 :               : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
      68             :                     "dPorousFlow_fluid_phase_density_qp_dvar")),
      69        5204 :     _fluid_viscosity(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
      70             :         "PorousFlow_viscosity_nodal")),
      71        2602 :     _dfluid_viscosity_dvar(
      72             :         is_ad ? nullptr
      73        2539 :               : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
      74             :                     "dPorousFlow_viscosity_nodal_dvar")),
      75        5204 :     _pp(this->template getGenericMaterialProperty<std::vector<Real>, is_ad>(
      76             :         "PorousFlow_porepressure_nodal")),
      77        5204 :     _grad_p(this->template getGenericMaterialProperty<std::vector<RealGradient>, is_ad>(
      78             :         "PorousFlow_grad_porepressure_qp")),
      79        2602 :     _dgrad_p_dgrad_var(is_ad ? nullptr
      80        2539 :                              : &this->template getMaterialProperty<std::vector<std::vector<Real>>>(
      81             :                                    "dPorousFlow_grad_porepressure_qp_dgradvar")),
      82        2602 :     _dgrad_p_dvar(is_ad
      83             :                       ? nullptr
      84        2539 :                       : &this->template getMaterialProperty<std::vector<std::vector<RealGradient>>>(
      85             :                             "dPorousFlow_grad_porepressure_qp_dvar")),
      86        2602 :     _dictator(this->template getUserObject<PorousFlowDictator>("PorousFlowDictator")),
      87        2602 :     _num_phases(_dictator.numPhases()),
      88        5204 :     _gravity(this->template getParam<RealVectorValue>("gravity")),
      89        2602 :     _perm_derivs(_dictator.usePermDerivs()),
      90        5204 :     _full_upwind_threshold(this->template getParam<unsigned>("full_upwind_threshold")),
      91        2602 :     _fallback_scheme(
      92        2602 :         this->template getParam<MooseEnum>("fallback_scheme").template getEnum<FallbackEnum>()),
      93        2602 :     _proto_flux(_num_phases),
      94        2602 :     _jacobian(_num_phases),
      95        2602 :     _num_upwinds(),
      96        2602 :     _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        2602 :   if (!_var.isNodal())
     101           2 :     mooseError("The variable '",
     102           2 :                _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        2600 : }
     113             : 
     114             : template <bool is_ad>
     115             : void
     116       25002 : PorousFlowDarcyBaseTempl<is_ad>::timestepSetup()
     117             : {
     118       25002 :   GenericKernel<is_ad>::timestepSetup();
     119       25002 :   _num_upwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
     120       25002 :   _num_downwinds = std::unordered_map<unsigned, std::vector<std::vector<unsigned>>>();
     121       25002 : }
     122             : 
     123             : template <bool is_ad>
     124             : void
     125       58270 : PorousFlowDarcyBaseTempl<is_ad>::jacobianSetup()
     126             : {
     127       58270 :   GenericKernel<is_ad>::jacobianSetup();
     128       58270 :   _my_elem_darcy = nullptr;
     129       58270 : }
     130             : 
     131             : template <bool is_ad>
     132             : GenericReal<is_ad>
     133   199252576 : PorousFlowDarcyBaseTempl<is_ad>::darcyQp(unsigned int ph) const
     134             : {
     135   199252576 :   return _grad_test[_i][_qp] *
     136   199252576 :          (_permeability[_qp] * (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity));
     137             : }
     138             : 
     139             : template <bool is_ad>
     140             : Real
     141   648261696 : PorousFlowDarcyBaseTempl<is_ad>::darcyQpJacobian(unsigned int jvar, unsigned int ph) const
     142             : {
     143             :   if constexpr (!is_ad)
     144             :   {
     145   648261696 :     if (_dictator.notPorousFlowVariable(jvar))
     146             :       return 0.0;
     147             : 
     148   648261696 :     const unsigned int pvar = _dictator.porousFlowVariableNum(jvar);
     149             : 
     150             :     RealVectorValue deriv =
     151   648261696 :         _permeability[_qp] * (_grad_phi[_j][_qp] * (*_dgrad_p_dgrad_var)[_qp][ph][pvar] -
     152   648261696 :                               _phi[_j][_qp] * (*_dfluid_density_qp_dvar)[_qp][ph][pvar] * _gravity);
     153             : 
     154   648261696 :     deriv += _permeability[_qp] * ((*_dgrad_p_dvar)[_qp][ph][pvar] * _phi[_j][_qp]);
     155             : 
     156   648261696 :     if (_perm_derivs)
     157             :     {
     158     4853504 :       deriv += (*_dpermeability_dvar)[_qp][pvar] * _phi[_j][_qp] *
     159     4853504 :                (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
     160    19414016 :       for (const auto i : make_range(Moose::dim))
     161    14560512 :         deriv += (*_dpermeability_dgradvar)[_qp][i][pvar] * _grad_phi[_j][_qp](i) *
     162    14560512 :                  (_grad_p[_qp][ph] - _fluid_density_qp[_qp][ph] * _gravity);
     163             :     }
     164             : 
     165   648261696 :     return _grad_test[_i][_qp] * deriv;
     166             :   }
     167             :   else
     168             :     libmesh_ignore(jvar, ph);
     169           0 :   return 0.0;
     170             : }
     171             : 
     172             : template <bool is_ad>
     173             : GenericReal<is_ad>
     174           0 : PorousFlowDarcyBaseTempl<is_ad>::computeQpResidual()
     175             : {
     176           0 :   mooseError("PorousFlowDarcyBase: computeQpResidual called");
     177             :   return 0.0;
     178             : }
     179             : 
     180             : template <bool is_ad>
     181             : void
     182     3542878 : PorousFlowDarcyBaseTempl<is_ad>::computeResidual()
     183             : {
     184             :   if constexpr (!is_ad)
     185     3542302 :     computeResidualAndJacobian(JacRes::CALCULATE_RESIDUAL, 0);
     186             :   else
     187             :   {
     188         576 :     adComputeProtoFlux(false);
     189             :     // Assemble the real-valued residual from the value parts of the ADReal proto fluxes
     190         576 :     assembleProtoFluxResidual();
     191             :   }
     192     3542878 : }
     193             : 
     194             : template <bool is_ad>
     195             : void
     196     3542878 : PorousFlowDarcyBaseTempl<is_ad>::assembleProtoFluxResidual()
     197             : {
     198     3542878 :   this->prepareVectorTag(this->_assembly, _var.number());
     199    16481322 :   for (_i = 0; _i < _test.size(); _i++)
     200    27818872 :     for (unsigned int ph = 0; ph < _num_phases; ++ph)
     201    14880428 :       this->_local_re(_i) += MetaPhysicL::raw_value(_proto_flux[ph][_i]);
     202     3542878 :   this->accumulateTaggedLocalResidual();
     203             : 
     204     3542878 :   if (this->_has_save_in)
     205           0 :     for (unsigned int i = 0; i < this->_save_in.size(); i++)
     206           0 :       this->_save_in[i]->sys().solution().add_vector(this->_local_re,
     207           0 :                                                      this->_save_in[i]->dofIndices());
     208     3542878 : }
     209             : 
     210             : template <bool is_ad>
     211             : void
     212         336 : PorousFlowDarcyBaseTempl<is_ad>::computeJacobian()
     213             : {
     214             :   if constexpr (!is_ad)
     215         336 :     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           0 :     adComputeJacobian();
     219         336 : }
     220             : 
     221             : template <bool is_ad>
     222             : void
     223          48 : PorousFlowDarcyBaseTempl<is_ad>::adComputeJacobian()
     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          48 :     adComputeProtoFlux(true);
     237             : 
     238          48 :     const unsigned int num_nodes = _test.size();
     239          48 :     std::vector<ADReal> darcy_residuals(num_nodes, 0.0);
     240         192 :     for (const auto n : make_range(num_nodes))
     241         288 :       for (const auto ph : make_range(_num_phases))
     242         144 :         darcy_residuals[n] += _proto_flux[ph][n];
     243             : 
     244          48 :     this->addJacobianWithoutConstraints(
     245          48 :         this->_assembly, darcy_residuals, this->dofIndices(), _var.scalingFactor());
     246          48 :   }
     247          48 : }
     248             : 
     249             : template <bool is_ad>
     250             : void
     251     4281414 : PorousFlowDarcyBaseTempl<is_ad>::computeOffDiagJacobian(const unsigned int jvar)
     252             : {
     253             :   if constexpr (!is_ad)
     254     4281302 :     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         112 :     if (_my_elem_darcy != this->_current_elem)
     262             :     {
     263          48 :       adComputeJacobian();
     264          48 :       _my_elem_darcy = this->_current_elem;
     265             :     }
     266             :   }
     267     4281414 : }
     268             : 
     269             : template <bool is_ad>
     270             : void
     271     7824564 : PorousFlowDarcyBaseTempl<is_ad>::computeProtoFluxWithoutMobility()
     272             : {
     273     7824564 :   const unsigned int num_nodes = _test.size();
     274    17403721 :   for (unsigned ph = 0; ph < _num_phases; ++ph)
     275             :   {
     276     9579157 :     _proto_flux[ph].assign(num_nodes, 0.0);
     277    46040581 :     for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
     278             :     {
     279    36461424 :       const Real jxw_coord = this->_JxW[_qp] * this->_coord[_qp];
     280   235714000 :       for (_i = 0; _i < num_nodes; ++_i)
     281   199259392 :         _proto_flux[ph][_i] += jxw_coord * darcyQp(ph);
     282             :     }
     283             :   }
     284     7824564 : }
     285             : 
     286             : template <bool is_ad>
     287             : void
     288     7824564 : PorousFlowDarcyBaseTempl<is_ad>::initializeUpwindTracking(unsigned elem, unsigned int num_nodes)
     289             : {
     290     7824564 :   if (_num_upwinds.find(elem) == _num_upwinds.end())
     291             :   {
     292     1793334 :     _num_upwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
     293     1793334 :     _num_downwinds[elem] = std::vector<std::vector<unsigned>>(_num_phases);
     294     1916185 :     for (unsigned ph = 0; ph < _num_phases; ++ph)
     295             :     {
     296     1019518 :       _num_upwinds[elem][ph].assign(num_nodes, 0);
     297     1019518 :       _num_downwinds[elem][ph].assign(num_nodes, 0);
     298             :     }
     299             :   }
     300     7824564 : }
     301             : 
     302             : template <bool is_ad>
     303             : void
     304     2073075 : PorousFlowDarcyBaseTempl<is_ad>::updateUpwindCounts(unsigned elem, unsigned int num_nodes)
     305             : {
     306     4606532 :   for (unsigned ph = 0; ph < _num_phases; ++ph)
     307    11391251 :     for (unsigned nod = 0; nod < num_nodes; ++nod)
     308             :     {
     309     8857794 :       if (_proto_flux[ph][nod] > 0)
     310     4291519 :         _num_upwinds[elem][ph][nod]++;
     311     4566275 :       else if (_proto_flux[ph][nod] < 0)
     312     4235731 :         _num_downwinds[elem][ph][nod]++;
     313             :     }
     314     2073075 : }
     315             : 
     316             : template <bool is_ad>
     317             : std::vector<unsigned>
     318     7824564 : PorousFlowDarcyBaseTempl<is_ad>::computeMaxSwaps(unsigned elem, unsigned int num_nodes) const
     319             : {
     320     7824564 :   std::vector<unsigned> max_swaps(_num_phases, 0);
     321    17403721 :   for (unsigned ph = 0; ph < _num_phases; ++ph)
     322    43097329 :     for (unsigned nod = 0; nod < num_nodes; ++nod)
     323    33518172 :       max_swaps[ph] =
     324             :           std::max(max_swaps[ph],
     325    33518172 :                    std::min(_num_upwinds.at(elem)[ph][nod], _num_downwinds.at(elem)[ph][nod]));
     326     7824564 :   return max_swaps;
     327           0 : }
     328             : 
     329             : template <bool is_ad>
     330             : void
     331     7824564 : PorousFlowDarcyBaseTempl<is_ad>::applyUpwinding(const std::vector<unsigned> & max_swaps,
     332             :                                                 JacRes res_or_jac,
     333             :                                                 unsigned int pvar)
     334             : {
     335    17403721 :   for (unsigned int ph = 0; ph < _num_phases; ++ph)
     336             :   {
     337     9579157 :     if (max_swaps[ph] < _full_upwind_threshold)
     338     9577925 :       fullyUpwind(res_or_jac, ph, pvar);
     339             :     else
     340             :     {
     341        1232 :       switch (_fallback_scheme)
     342             :       {
     343         432 :         case FallbackEnum::QUICK:
     344         432 :           quickUpwind(res_or_jac, ph, pvar);
     345         432 :           break;
     346         800 :         case FallbackEnum::HARMONIC:
     347         800 :           harmonicMean(res_or_jac, ph, pvar);
     348         800 :           break;
     349             :       }
     350             :     }
     351             :   }
     352     7824564 : }
     353             : 
     354             : template <bool is_ad>
     355             : void
     356         624 : PorousFlowDarcyBaseTempl<is_ad>::adComputeProtoFlux(bool do_counting)
     357             : {
     358         624 :   const unsigned int num_nodes = _test.size();
     359         624 :   computeProtoFluxWithoutMobility();
     360             : 
     361             :   // Initialise upwind-tracking maps for this element on first encounter
     362         624 :   const unsigned elem = this->_current_elem->id();
     363         624 :   initializeUpwindTracking(elem, num_nodes);
     364             : 
     365         624 :   if (do_counting)
     366          48 :     updateUpwindCounts(elem, num_nodes);
     367             : 
     368             :   // Determine how many upwind-downwind swaps have occurred this timestep
     369         624 :   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         624 :   applyUpwinding(max_swaps, JacRes::CALCULATE_RESIDUAL, 0);
     373         624 : }
     374             : 
     375             : template <bool is_ad>
     376             : void
     377     7823940 : PorousFlowDarcyBaseTempl<is_ad>::computeResidualAndJacobian(JacRes res_or_jac, unsigned int jvar)
     378             : {
     379     7823940 :   if ((res_or_jac == JacRes::CALCULATE_JACOBIAN) && _dictator.notPorousFlowVariable(jvar))
     380           0 :     return;
     381             : 
     382             :   // The PorousFlow variable index corresponding to the variable number jvar
     383             :   const unsigned int pvar =
     384     7823940 :       ((res_or_jac == JacRes::CALCULATE_JACOBIAN) ? _dictator.porousFlowVariableNum(jvar) : 0);
     385             : 
     386     7823940 :   this->prepareMatrixTag(this->_assembly, _var.number(), jvar);
     387     7823940 :   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     7823940 :   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     7823940 :   computeProtoFluxWithoutMobility();
     400             : 
     401             :   // for this element, record whether each node is "upwind" or "downwind" (or neither)
     402     7823940 :   const unsigned elem = this->_current_elem->id();
     403     7823940 :   initializeUpwindTracking(elem, num_nodes);
     404             :   // record the information once per nonlinear iteration
     405     7823940 :   if (res_or_jac == JacRes::CALCULATE_JACOBIAN && jvar == _var.number())
     406     2073027 :     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     7823940 :   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     7823940 :   if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     415             :   {
     416     9634140 :     for (unsigned ph = 0; ph < _num_phases; ++ph)
     417             :     {
     418     5352502 :       _jacobian[ph].resize(this->_local_ke.m());
     419    23990102 :       for (_i = 0; _i < _test.size(); _i++)
     420             :       {
     421    18637600 :         _jacobian[ph][_i].assign(this->_local_ke.n(), 0.0);
     422   101594820 :         for (_j = 0; _j < _phi.size(); _j++)
     423   731218916 :           for (_qp = 0; _qp < this->_qrule->n_points(); _qp++)
     424   648261696 :             _jacobian[ph][_i][_j] +=
     425   648261696 :                 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     7823940 :   applyUpwinding(max_swaps, res_or_jac, pvar);
     434             : 
     435             :   // Add results to the Residual or Jacobian
     436     7823940 :   if (res_or_jac == JacRes::CALCULATE_RESIDUAL)
     437     3542302 :     assembleProtoFluxResidual();
     438             : 
     439     7823940 :   if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     440             :   {
     441    20040182 :     for (_i = 0; _i < _test.size(); _i++)
     442    90004884 :       for (_j = 0; _j < _phi.size(); _j++)
     443   157203560 :         for (unsigned int ph = 0; ph < _num_phases; ++ph)
     444    82957220 :           this->_local_ke(_i, _j) += _jacobian[ph][_i][_j];
     445             : 
     446     4281638 :     this->accumulateTaggedLocalMatrix();
     447             : 
     448     4281638 :     if (this->_has_diag_save_in && jvar == _var.number())
     449             :     {
     450             :       unsigned int rows = this->_local_ke.m();
     451           0 :       DenseVector<Number> diag(rows);
     452           0 :       for (unsigned int i = 0; i < rows; i++)
     453           0 :         diag(i) = this->_local_ke(i, i);
     454             : 
     455           0 :       for (unsigned int i = 0; i < this->_diag_save_in.size(); i++)
     456           0 :         this->_diag_save_in[i]->sys().solution().add_vector(diag,
     457           0 :                                                             this->_diag_save_in[i]->dofIndices());
     458             :     }
     459             :   }
     460     7823940 : }
     461             : 
     462             : template <bool is_ad>
     463             : void
     464     9577925 : 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             : 
     470             :   /**
     471             :    * Perform the full upwinding by multiplying the residuals at the upstream nodes by their
     472             :    * mobilities.
     473             :    * Mobility is different for each phase, and in each situation:
     474             :    *   mobility = density / viscosity    for single-component Darcy flow
     475             :    *   mobility = mass_fraction * density * relative_perm / viscosity    for multi-component,
     476             :    *multiphase flow
     477             :    *   mobility = enthalpy * density * relative_perm / viscosity    for heat convection
     478             :    *
     479             :    * The residual for the kernel is the sum over Darcy fluxes for each phase.
     480             :    * The Darcy flux for a particular phase is
     481             :    * R_i = int{mobility*flux_no_mob} = int{mobility*grad(pot)*permeability*grad(test_i)}
     482             :    * for node i.  where int is the integral over the element.
     483             :    * However, in fully-upwind, the first step is to take the mobility outside the integral,
     484             :    * which was done in the _proto_flux calculation above.
     485             :    *
     486             :    * NOTE: Physically _proto_flux[i][ph] is a measure of fluid of phase ph flowing out of node i.
     487             :    * If we had left in mobility, it would be exactly the component mass flux flowing out of node
     488             :    *i.
     489             :    *
     490             :    * This leads to the definition of upwinding:
     491             :    *
     492             :    * If _proto_flux(i)[ph] is positive then we use mobility_i.  That is we use the upwind value of
     493             :    * mobility.
     494             :    *
     495             :    * The final subtle thing is we must also conserve fluid mass: the total component mass flowing
     496             :    * out of node i must be the sum of the masses flowing into the other nodes.
     497             :    **/
     498             : 
     499             :   // The number of nodes in the element
     500     9577925 :   const unsigned int num_nodes = _test.size();
     501             : 
     502             :   GenericReal<is_ad> mob;
     503             :   // Define variables used to ensure mass conservation
     504         624 :   GenericReal<is_ad> total_mass_out = 0.0;
     505         624 :   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     9577301 :     if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     512             :     {
     513     5352342 :       dtotal_mass_out.assign(num_nodes, 0.0);
     514     5352342 :       dtotal_in.assign(num_nodes, 0.0);
     515             :     }
     516             : 
     517             :   // Perform the upwinding using the mobility
     518     9577925 :   std::vector<bool> upwind_node(num_nodes);
     519    43091233 :   for (unsigned int n = 0; n < num_nodes; ++n)
     520             :   {
     521    33513308 :     if (_proto_flux[ph][n] >= 0.0) // upstream node
     522             :     {
     523             :       upwind_node[n] = true;
     524             :       // The mobility at the upstream node
     525    17371159 :       mob = mobility(n, ph);
     526             :       if constexpr (!is_ad)
     527    17370179 :         if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     528             :         {
     529             :           // The derivative of the mobility wrt the PorousFlow variable
     530     9698873 :           const Real dmob = dmobility(n, ph, pvar);
     531             : 
     532    53005013 :           for (_j = 0; _j < _phi.size(); _j++)
     533    43306140 :             _jacobian[ph][n][_j] *= mob;
     534             : 
     535     9698873 :           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     8458326 :             _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
     545             : 
     546    53005013 :           for (_j = 0; _j < _phi.size(); _j++)
     547    43306140 :             dtotal_mass_out[_j] += _jacobian[ph][n][_j];
     548             :         }
     549    17371159 :       _proto_flux[ph][n] *= mob;
     550    17371159 :       total_mass_out += _proto_flux[ph][n];
     551             :     }
     552             :     else
     553             :     {
     554             :       upwind_node[n] = false;
     555    16142149 :       total_in -= _proto_flux[ph][n]; /// note the -= means the result is positive
     556             :       if constexpr (!is_ad)
     557    16141161 :         if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     558    48586831 :           for (_j = 0; _j < _phi.size(); _j++)
     559    39648712 :             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    43091233 :   for (unsigned int n = 0; n < num_nodes; ++n)
     566             :   {
     567    33513308 :     if (!upwind_node[n]) // downstream node
     568             :     {
     569             :       if constexpr (!is_ad)
     570    16141161 :         if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     571    48586831 :           for (_j = 0; _j < _phi.size(); _j++)
     572             :           {
     573    39648712 :             _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(total_mass_out / total_in);
     574    39648712 :             _jacobian[ph][n][_j] +=
     575    39648712 :                 MetaPhysicL::raw_value(_proto_flux[ph][n]) *
     576    39648712 :                 (dtotal_mass_out[_j] / MetaPhysicL::raw_value(total_in) -
     577    39648712 :                  dtotal_in[_j] * MetaPhysicL::raw_value(total_mass_out) /
     578    39648712 :                      MetaPhysicL::raw_value(total_in) / MetaPhysicL::raw_value(total_in));
     579             :           }
     580    16142149 :       _proto_flux[ph][n] *= total_mass_out / total_in;
     581             :     }
     582             :   }
     583     9577925 : }
     584             : 
     585             : template <bool is_ad>
     586             : void
     587         432 : 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         432 :   const unsigned int num_nodes = _test.size();
     595             : 
     596             :   // Use the raw nodal mobility
     597        2096 :   for (unsigned int n = 0; n < num_nodes; ++n)
     598             :   {
     599             :     // The mobility at the node
     600        1664 :     const GenericReal<is_ad> mob = mobility(n, ph);
     601             :     if constexpr (!is_ad)
     602        1664 :       if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     603             :       {
     604             :         // The derivative of the mobility wrt the PorousFlow variable
     605         224 :         const Real dmob = dmobility(n, ph, pvar);
     606             : 
     607        1056 :         for (_j = 0; _j < _phi.size(); _j++)
     608         832 :           _jacobian[ph][n][_j] *= mob;
     609             : 
     610         224 :         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         224 :           _jacobian[ph][n][n] += dmob * MetaPhysicL::raw_value(_proto_flux[ph][n]);
     620             :       }
     621        1664 :     _proto_flux[ph][n] *= mob;
     622             :   }
     623         432 : }
     624             : 
     625             : template <bool is_ad>
     626             : void
     627         800 : 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         800 :   const unsigned int num_nodes = _test.size();
     635             : 
     636         800 :   std::vector<GenericReal<is_ad>> mob(num_nodes);
     637             :   unsigned num_zero = 0;
     638           0 :   GenericReal<is_ad> harmonic_mob = 0;
     639        4000 :   for (unsigned n = 0; n < num_nodes; ++n)
     640             :   {
     641        3200 :     mob[n] = mobility(n, ph);
     642        3200 :     if (MetaPhysicL::raw_value(mob[n]) == 0.0)
     643             :     {
     644           0 :       num_zero++;
     645             :     }
     646             :     else
     647        3200 :       harmonic_mob += 1.0 / mob[n];
     648             :   }
     649         800 :   if (num_zero > 0)
     650           0 :     harmonic_mob = 0.0;
     651             :   else
     652         800 :     harmonic_mob = (1.0 * num_nodes) / harmonic_mob;
     653             : 
     654             :   // d(harmonic_mob)/d(PorousFlow variable at node n) -- non-AD path only
     655         800 :   std::vector<Real> dharmonic_mob(num_nodes, 0.0);
     656             :   if constexpr (!is_ad)
     657         800 :     if (res_or_jac == JacRes::CALCULATE_JACOBIAN)
     658             :     {
     659          96 :       const Real harm2 = std::pow(MetaPhysicL::raw_value(harmonic_mob), 2) / (1.0 * num_nodes);
     660          96 :       if (num_zero == 0)
     661         480 :         for (unsigned n = 0; n < num_nodes; ++n)
     662         384 :           dharmonic_mob[n] =
     663         384 :               dmobility(n, ph, pvar) * harm2 / std::pow(MetaPhysicL::raw_value(mob[n]), 2);
     664           0 :       else if (num_zero == 1)
     665           0 :         for (unsigned n = 0; n < num_nodes; ++n)
     666           0 :           if (MetaPhysicL::raw_value(mob[n]) == 0.0)
     667             :           {
     668           0 :             dharmonic_mob[n] = num_nodes * dmobility(n, ph, pvar); // other derivs are zero
     669           0 :             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         480 :       for (unsigned n = 0; n < num_nodes; ++n)
     677        1920 :         for (_j = 0; _j < _phi.size(); _j++)
     678             :         {
     679        1536 :           _jacobian[ph][n][_j] *= MetaPhysicL::raw_value(harmonic_mob);
     680        1536 :           if (_test.size() == _phi.size())
     681        1536 :             _jacobian[ph][n][_j] += dharmonic_mob[_j] * MetaPhysicL::raw_value(_proto_flux[ph][n]);
     682             :         }
     683             : 
     684        4000 :   for (unsigned n = 0; n < num_nodes; ++n)
     685        3200 :     _proto_flux[ph][n] *= harmonic_mob;
     686         800 : }
     687             : 
     688             : template <bool is_ad>
     689             : GenericReal<is_ad>
     690           0 : PorousFlowDarcyBaseTempl<is_ad>::mobility(unsigned nodenum, unsigned phase) const
     691             : {
     692           0 :   return _fluid_density_node[nodenum][phase] / _fluid_viscosity[nodenum][phase];
     693             : }
     694             : 
     695             : template <bool is_ad>
     696             : Real
     697           0 : PorousFlowDarcyBaseTempl<is_ad>::dmobility(unsigned nodenum, unsigned phase, unsigned pvar) const
     698             : {
     699             :   if constexpr (!is_ad)
     700             :   {
     701           0 :     Real dm = (*_dfluid_density_node_dvar)[nodenum][phase][pvar] / _fluid_viscosity[nodenum][phase];
     702           0 :     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           0 :   return 0.0;
     709             : }
     710             : 
     711             : template class PorousFlowDarcyBaseTempl<false>;
     712             : template class PorousFlowDarcyBaseTempl<true>;

Generated by: LCOV version 1.14