LCOV - code coverage report
Current view: top level - src/kernels - RichardsFullyUpwindFlux.C (source / functions) Hit Total Coverage
Test: idaholab/moose richards: #31405 (292dce) with base fef103 Lines: 122 123 99.2 %
Date: 2025-09-04 07:56:35 Functions: 8 8 100.0 %
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 "RichardsFullyUpwindFlux.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "Assembly.h"
      14             : #include "MooseVariable.h"
      15             : #include "SystemBase.h"
      16             : 
      17             : #include "libmesh/quadrature.h"
      18             : 
      19             : registerMooseObject("RichardsApp", RichardsFullyUpwindFlux);
      20             : 
      21             : InputParameters
      22         194 : RichardsFullyUpwindFlux::validParams()
      23             : {
      24         194 :   InputParameters params = Kernel::validParams();
      25         388 :   params.addRequiredParam<std::vector<UserObjectName>>(
      26             :       "relperm_UO", "List of names of user objects that define relative permeability");
      27         388 :   params.addRequiredParam<std::vector<UserObjectName>>(
      28             :       "seff_UO",
      29             :       "List of name of user objects that define effective saturation as a function of "
      30             :       "pressure list");
      31         388 :   params.addRequiredParam<std::vector<UserObjectName>>(
      32             :       "density_UO", "List of names of user objects that define the fluid density");
      33         388 :   params.addRequiredParam<UserObjectName>(
      34             :       "richardsVarNames_UO", "The UserObject that holds the list of Richards variable names.");
      35         194 :   return params;
      36           0 : }
      37             : 
      38          97 : RichardsFullyUpwindFlux::RichardsFullyUpwindFlux(const InputParameters & parameters)
      39             :   : Kernel(parameters),
      40          97 :     _richards_name_UO(getUserObject<RichardsVarNames>("richardsVarNames_UO")),
      41          97 :     _num_p(_richards_name_UO.num_v()),
      42          97 :     _pvar(_richards_name_UO.richards_var_num(_var.number())),
      43          97 :     _density_UO(getUserObjectByName<RichardsDensity>(
      44         194 :         getParam<std::vector<UserObjectName>>("density_UO")[_pvar])),
      45          97 :     _seff_UO(
      46         194 :         getUserObjectByName<RichardsSeff>(getParam<std::vector<UserObjectName>>("seff_UO")[_pvar])),
      47          97 :     _relperm_UO(getUserObjectByName<RichardsRelPerm>(
      48         194 :         getParam<std::vector<UserObjectName>>("relperm_UO")[_pvar])),
      49         194 :     _viscosity(getMaterialProperty<std::vector<Real>>("viscosity")),
      50         194 :     _flux_no_mob(getMaterialProperty<std::vector<RealVectorValue>>("flux_no_mob")),
      51          97 :     _dflux_no_mob_dv(
      52          97 :         getMaterialProperty<std::vector<std::vector<RealVectorValue>>>("dflux_no_mob_dv")),
      53          97 :     _dflux_no_mob_dgradv(
      54          97 :         getMaterialProperty<std::vector<std::vector<RealTensorValue>>>("dflux_no_mob_dgradv")),
      55          97 :     _num_nodes(0),
      56          97 :     _mobility(0),
      57         194 :     _dmobility_dv(0)
      58             : {
      59          97 :   _ps_at_nodes.resize(_num_p);
      60         250 :   for (unsigned int pnum = 0; pnum < _num_p; ++pnum)
      61         153 :     _ps_at_nodes[pnum] = _richards_name_UO.nodal_var(pnum);
      62          97 : }
      63             : 
      64             : void
      65      696958 : RichardsFullyUpwindFlux::prepareNodalValues()
      66             : {
      67      696958 :   _num_nodes = (*_ps_at_nodes[_pvar]).size();
      68             : 
      69             :   Real p;
      70             :   Real density;
      71             :   Real ddensity_dp;
      72             :   Real seff;
      73             :   std::vector<Real> dseff_dp;
      74             :   Real relperm;
      75             :   Real drelperm_ds;
      76      696958 :   _mobility.resize(_num_nodes);
      77      696958 :   _dmobility_dv.resize(_num_nodes);
      78      696958 :   dseff_dp.resize(_num_p);
      79     2717996 :   for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
      80             :   {
      81             :     // retrieve and calculate basic things at the node
      82     2021038 :     p = (*_ps_at_nodes[_pvar])[nodenum];   // pressure of fluid _pvar at node nodenum
      83     2021038 :     density = _density_UO.density(p);      // density of fluid _pvar at node nodenum
      84     2021038 :     ddensity_dp = _density_UO.ddensity(p); // d(density)/dP
      85             :     seff =
      86     2021038 :         _seff_UO.seff(_ps_at_nodes, nodenum); // effective saturation of fluid _pvar at node nodenum
      87     2021038 :     _seff_UO.dseff(_ps_at_nodes, nodenum, dseff_dp); // d(seff)/d(P_ph), for ph = 0, ..., _num_p - 1
      88     2021038 :     relperm = _relperm_UO.relperm(seff); // relative permeability of fluid _pvar at node nodenum
      89     2021038 :     drelperm_ds = _relperm_UO.drelperm(seff); // d(relperm)/dseff
      90             : 
      91             :     // calculate the mobility and its derivatives wrt (variable_ph = porepressure_ph)
      92     2021038 :     _mobility[nodenum] =
      93     2021038 :         density * relperm / _viscosity[0][_pvar]; // assume viscosity is constant throughout element
      94     2021038 :     _dmobility_dv[nodenum].resize(_num_p);
      95     4991356 :     for (unsigned int ph = 0; ph < _num_p; ++ph)
      96     2970318 :       _dmobility_dv[nodenum][ph] = density * drelperm_ds * dseff_dp[ph] / _viscosity[0][_pvar];
      97     2021038 :     _dmobility_dv[nodenum][_pvar] += ddensity_dp * relperm / _viscosity[0][_pvar];
      98             :   }
      99      696958 : }
     100             : 
     101             : Real
     102     7731052 : RichardsFullyUpwindFlux::computeQpResidual()
     103             : {
     104             :   // note this is not the complete residual:
     105             :   // the upwind mobility parts get added in computeResidual
     106     7731052 :   return _grad_test[_i][_qp] * _flux_no_mob[_qp][_pvar];
     107             : }
     108             : 
     109             : void
     110      430110 : RichardsFullyUpwindFlux::computeResidual()
     111             : {
     112      430110 :   upwind(true, false, 0);
     113      430110 :   return;
     114             : }
     115             : 
     116             : void
     117      266850 : RichardsFullyUpwindFlux::computeOffDiagJacobian(const unsigned int jvar)
     118             : {
     119      266850 :   upwind(false, true, jvar);
     120      266850 :   return;
     121             : }
     122             : 
     123             : Real
     124    18334856 : RichardsFullyUpwindFlux::computeQpJac(unsigned int dvar)
     125             : {
     126             :   // this is just the derivative of the flux WITHOUT the upstream mobility terms
     127             :   // Those terms get added in during computeJacobian()
     128    18334856 :   return _grad_test[_i][_qp] * (_dflux_no_mob_dgradv[_qp][_pvar][dvar] * _grad_phi[_j][_qp] +
     129    18334856 :                                 _dflux_no_mob_dv[_qp][_pvar][dvar] * _phi[_j][_qp]);
     130             : }
     131             : 
     132             : void
     133      696960 : RichardsFullyUpwindFlux::upwind(bool compute_res, bool compute_jac, unsigned int jvar)
     134             : {
     135      696960 :   if (compute_jac && _richards_name_UO.not_richards_var(jvar))
     136           2 :     return;
     137             : 
     138             :   // calculate the mobility values and their derivatives
     139      696958 :   prepareNodalValues();
     140             : 
     141             :   // compute the residual without the mobility terms
     142             :   // Even if we are computing the jacobian we still need this
     143             :   // in order to see which nodes are upwind and which are downwind
     144      696958 :   prepareVectorTag(_assembly, _var.number());
     145     2717996 :   for (_i = 0; _i < _test.size(); _i++)
     146     9752090 :     for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     147     7731052 :       _local_re(_i) += _JxW[_qp] * _coord[_qp] * computeQpResidual();
     148             : 
     149      696958 :   const unsigned int dvar = _richards_name_UO.richards_var_num(jvar);
     150      696958 :   if (compute_jac)
     151             :   {
     152      266848 :     prepareMatrixTag(_assembly, _var.number(), jvar);
     153     1127802 :     for (_i = 0; _i < _test.size(); _i++)
     154     4394526 :       for (_j = 0; _j < _phi.size(); _j++)
     155    21868428 :         for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     156    18334856 :           _local_ke(_i, _j) += _JxW[_qp] * _coord[_qp] * computeQpJac(dvar);
     157             :   }
     158             : 
     159             :   // Now perform the upwinding by multiplying the residuals at the
     160             :   // upstream nodes by their mobilities
     161             :   //
     162             :   // The residual for the kernel is the darcy flux.
     163             :   // This is
     164             :   // R_i = int{mobility*flux_no_mob} = int{mobility*grad(pot)*permeability*grad(test_i)}
     165             :   // for node i.  where int is the integral over the element.
     166             :   // However, in fully-upwind, the first step is to take the mobility outside the
     167             :   // integral, which was done in the _local_re calculation above.
     168             :   //
     169             :   // NOTE: Physically _local_re(_i) is a measure of fluid flowing out of node i
     170             :   // If we had left in mobility, it would be exactly the mass flux flowing out of node i.
     171             :   //
     172             :   // This leads to the definition of upwinding:
     173             :   // ***
     174             :   // If _local_re(i) is positive then we use mobility_i.  That is
     175             :   // we use the upwind value of mobility.
     176             :   // ***
     177             :   //
     178             :   // The final subtle thing is we must also conserve fluid mass: the total mass
     179             :   // flowing out of node i must be the sum of the masses flowing
     180             :   // into the other nodes.
     181             : 
     182             :   // FIRST:
     183             :   // this is a dirty way of getting around precision loss problems
     184             :   // and problems at steadystate where upwinding oscillates from
     185             :   // node-to-node causing nonconvergence.
     186             :   // I'm not sure if i actually need to do this in moose.  Certainly
     187             :   // in cosflow it is necessary.
     188             :   // I will code a better algorithm if necessary
     189             :   bool reached_steady = true;
     190      976281 :   for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
     191             :   {
     192      962263 :     if (_local_re(nodenum) >= 1E-20)
     193             :     {
     194             :       reached_steady = false;
     195             :       break;
     196             :     }
     197             :   }
     198             : 
     199             :   // DEFINE VARIABLES USED TO ENSURE MASS CONSERVATION
     200             :   // total mass out - used for mass conservation
     201             :   Real total_mass_out = 0;
     202             :   // total flux in
     203             :   Real total_in = 0;
     204             : 
     205             :   // the following holds derivatives of these
     206             :   std::vector<Real> dtotal_mass_out;
     207             :   std::vector<Real> dtotal_in;
     208      696958 :   if (compute_jac)
     209             :   {
     210      266848 :     dtotal_mass_out.resize(_num_nodes);
     211      266848 :     dtotal_in.resize(_num_nodes);
     212     1127802 :     for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
     213             :     {
     214      860954 :       dtotal_mass_out[nodenum] = 0;
     215      860954 :       dtotal_in[nodenum] = 0;
     216             :     }
     217             :   }
     218             : 
     219             :   // PERFORM THE UPWINDING!
     220     2717996 :   for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
     221             :   {
     222     2021038 :     if (_local_re(nodenum) >= 0 || reached_steady) // upstream node
     223             :     {
     224     1030463 :       if (compute_jac)
     225             :       {
     226     2247910 :         for (_j = 0; _j < _phi.size(); _j++)
     227     1805722 :           _local_ke(nodenum, _j) *= _mobility[nodenum];
     228      442188 :         _local_ke(nodenum, nodenum) += _dmobility_dv[nodenum][dvar] * _local_re(nodenum);
     229     2247910 :         for (_j = 0; _j < _phi.size(); _j++)
     230     1805722 :           dtotal_mass_out[_j] += _local_ke(nodenum, _j);
     231             :       }
     232     1030463 :       _local_re(nodenum) *= _mobility[nodenum];
     233     1030463 :       total_mass_out += _local_re(nodenum);
     234             :     }
     235             :     else
     236             :     {
     237      990575 :       total_in -= _local_re(nodenum); // note the -= means the result is positive
     238      990575 :       if (compute_jac)
     239     2146616 :         for (_j = 0; _j < _phi.size(); _j++)
     240     1727850 :           dtotal_in[_j] -= _local_ke(nodenum, _j);
     241             :     }
     242             :   }
     243             : 
     244             :   // CONSERVE MASS
     245             :   // proportion the total_mass_out mass to the inflow nodes, weighting by their _local_re values
     246      696958 :   if (!reached_steady)
     247     2671142 :     for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
     248     1988202 :       if (_local_re(nodenum) < 0)
     249             :       {
     250      990575 :         if (compute_jac)
     251     2146616 :           for (_j = 0; _j < _phi.size(); _j++)
     252             :           {
     253     1727850 :             _local_ke(nodenum, _j) *= total_mass_out / total_in;
     254     1727850 :             _local_ke(nodenum, _j) +=
     255     1727850 :                 _local_re(nodenum) * (dtotal_mass_out[_j] / total_in -
     256     1727850 :                                       dtotal_in[_j] * total_mass_out / total_in / total_in);
     257             :           }
     258      990575 :         _local_re(nodenum) *= total_mass_out / total_in;
     259             :       }
     260             : 
     261             :   // ADD RESULTS TO RESIDUAL OR JACOBIAN
     262      696958 :   if (compute_res)
     263             :   {
     264      430110 :     accumulateTaggedLocalResidual();
     265      430110 :     if (_has_save_in)
     266           8 :       for (unsigned int i = 0; i < _save_in.size(); i++)
     267           4 :         _save_in[i]->sys().solution().add_vector(_local_re, _save_in[i]->dofIndices());
     268             :   }
     269             : 
     270      696958 :   if (compute_jac)
     271             :   {
     272      266848 :     accumulateTaggedLocalMatrix();
     273      266848 :     if (_has_diag_save_in && dvar == _pvar)
     274             :     {
     275             :       const unsigned int rows = _local_ke.m();
     276           2 :       DenseVector<Number> diag(rows);
     277           6 :       for (unsigned int i = 0; i < rows; i++)
     278           4 :         diag(i) = _local_ke(i, i);
     279             : 
     280           4 :       for (unsigned int i = 0; i < _diag_save_in.size(); i++)
     281           2 :         _diag_save_in[i]->sys().solution().add_vector(diag, _diag_save_in[i]->dofIndices());
     282             :     }
     283             :   }
     284      696958 : }

Generated by: LCOV version 1.14