LCOV - code coverage report
Current view: top level - src/kernels - Q2PPorepressureFlux.C (source / functions) Hit Total Coverage
Test: idaholab/moose richards: #31405 (292dce) with base fef103 Lines: 110 111 99.1 %
Date: 2025-09-04 07:56:35 Functions: 9 9 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 "Q2PPorepressureFlux.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             : // C++ includes
      20             : #include <iostream>
      21             : 
      22             : registerMooseObject("RichardsApp", Q2PPorepressureFlux);
      23             : 
      24             : InputParameters
      25          28 : Q2PPorepressureFlux::validParams()
      26             : {
      27          28 :   InputParameters params = Kernel::validParams();
      28          56 :   params.addRequiredParam<UserObjectName>(
      29             :       "fluid_density",
      30             :       "A RichardsDensity UserObject that defines the fluid density as a function of pressure.");
      31          56 :   params.addRequiredCoupledVar("saturation_variable", "The variable representing fluid saturation");
      32          56 :   params.addRequiredParam<UserObjectName>(
      33             :       "fluid_relperm",
      34             :       "A RichardsRelPerm UserObject (eg RichardsRelPermPower) that defines the "
      35             :       "fluid relative permeability as a function of the saturation variable");
      36          56 :   params.addRequiredParam<Real>("fluid_viscosity", "The fluid dynamic viscosity");
      37          28 :   params.addClassDescription("Flux according to Darcy-Richards flow.  The Variable for this Kernel "
      38             :                              "should be the porepressure.");
      39          28 :   return params;
      40           0 : }
      41             : 
      42          14 : Q2PPorepressureFlux::Q2PPorepressureFlux(const InputParameters & parameters)
      43             :   : Kernel(parameters),
      44          14 :     _density(getUserObject<RichardsDensity>("fluid_density")),
      45          14 :     _sat(coupledDofValues("saturation_variable")),
      46          14 :     _sat_var(coupled("saturation_variable")),
      47          14 :     _relperm(getUserObject<RichardsRelPerm>("fluid_relperm")),
      48          28 :     _viscosity(getParam<Real>("fluid_viscosity")),
      49          28 :     _gravity(getMaterialProperty<RealVectorValue>("gravity")),
      50          28 :     _permeability(getMaterialProperty<RealTensorValue>("permeability")),
      51          14 :     _num_nodes(0),
      52          14 :     _mobility(0),
      53          14 :     _dmobility_dp(0),
      54          28 :     _dmobility_ds(0)
      55             : {
      56          14 : }
      57             : 
      58             : void
      59       12006 : Q2PPorepressureFlux::prepareNodalValues()
      60             : {
      61       12006 :   _num_nodes = _sat.size();
      62             : 
      63             :   Real density;
      64             :   Real ddensity_dp;
      65             :   Real relperm;
      66             :   Real drelperm_ds;
      67             : 
      68       12006 :   _mobility.resize(_num_nodes);
      69       12006 :   _dmobility_dp.resize(_num_nodes);
      70       12006 :   _dmobility_ds.resize(_num_nodes);
      71             : 
      72       40398 :   for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
      73             :   {
      74       28392 :     density = _density.density(_var.dofValues()[nodenum]);      // fluid density at the node
      75       28392 :     ddensity_dp = _density.ddensity(_var.dofValues()[nodenum]); // d(fluid density)/dP at the node
      76       28392 :     relperm = _relperm.relperm(_sat[nodenum]); // relative permeability of the fluid at node nodenum
      77       28392 :     drelperm_ds = _relperm.drelperm(_sat[nodenum]); // d(relperm)/dsat
      78             : 
      79             :     // calculate the mobility and its derivatives wrt P and S
      80       28392 :     _mobility[nodenum] = density * relperm / _viscosity;
      81       28392 :     _dmobility_dp[nodenum] = ddensity_dp * relperm / _viscosity;
      82       28392 :     _dmobility_ds[nodenum] = density * drelperm_ds / _viscosity;
      83             :   }
      84       12006 : }
      85             : 
      86             : Real
      87       88032 : Q2PPorepressureFlux::computeQpResidual()
      88             : {
      89             :   // note this is not the complete residual:
      90             :   // the upwind mobility parts get added in computeResidual
      91       88032 :   return _grad_test[_i][_qp] *
      92       88032 :          (_permeability[_qp] * (_grad_u[_qp] - _density.density(_u[_qp]) * _gravity[_qp]));
      93             : }
      94             : 
      95             : void
      96        5535 : Q2PPorepressureFlux::computeResidual()
      97             : {
      98        5535 :   upwind(true, false, 0);
      99        5535 : }
     100             : 
     101             : void
     102          33 : Q2PPorepressureFlux::computeJacobian()
     103             : {
     104          33 :   upwind(false, true, _var.number());
     105          33 : }
     106             : 
     107             : void
     108        6439 : Q2PPorepressureFlux::computeOffDiagJacobian(const unsigned int jvar)
     109             : {
     110        6439 :   upwind(false, true, jvar);
     111        6439 : }
     112             : 
     113             : Real
     114      211648 : Q2PPorepressureFlux::computeQpJac(unsigned int dvar)
     115             : {
     116             :   // this is just the derivative of the flux WITHOUT the upstream mobility terms
     117             :   // Those terms get added in during computeJacobian()
     118      211648 :   if (dvar == _var.number())
     119      106880 :     return _grad_test[_i][_qp] *
     120      106880 :            (_permeability[_qp] *
     121      106880 :             (_grad_phi[_j][_qp] - _density.ddensity(_u[_qp]) * _gravity[_qp] * _phi[_j][_qp]));
     122             :   else
     123             :     return 0;
     124             : }
     125             : 
     126             : void
     127       12007 : Q2PPorepressureFlux::upwind(bool compute_res, bool compute_jac, unsigned int jvar)
     128             : {
     129       12007 :   if (compute_jac && !(jvar == _var.number() || jvar == _sat_var))
     130           1 :     return;
     131             : 
     132             :   // calculate the mobility values and their derivatives
     133       12006 :   prepareNodalValues();
     134             : 
     135             :   // compute the residual without the mobility terms
     136             :   // Even if we are computing the jacobian we still need this
     137             :   // in order to see which nodes are upwind and which are downwind
     138       12006 :   prepareVectorTag(_assembly, _var.number());
     139             : 
     140       40398 :   for (_i = 0; _i < _test.size(); _i++)
     141      116424 :     for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     142       88032 :       _local_re(_i) += _JxW[_qp] * _coord[_qp] * computeQpResidual();
     143             : 
     144       12006 :   if (compute_jac)
     145             :   {
     146        6471 :     prepareMatrixTag(_assembly, _var.number(), jvar);
     147       21547 :     for (_i = 0; _i < _test.size(); _i++)
     148       60916 :       for (_j = 0; _j < _phi.size(); _j++)
     149      257488 :         for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     150      211648 :           _local_ke(_i, _j) += _JxW[_qp] * _coord[_qp] * computeQpJac(jvar);
     151             :   }
     152             : 
     153             :   // Now perform the upwinding by multiplying the residuals at the
     154             :   // upstream nodes by their mobilities
     155             :   //
     156             :   // The residual for the kernel is the darcy flux.
     157             :   // This is
     158             :   // R_i = int{mobility*flux_no_mob} = int{mobility*grad(pot)*permeability*grad(test_i)}
     159             :   // for node i.  where int is the integral over the element.
     160             :   // However, in fully-upwind, the first step is to take the mobility outside the
     161             :   // integral, which was done in the _local_re calculation above.
     162             :   //
     163             :   // NOTE: Physically _local_re(_i) is a measure of fluid flowing out of node i
     164             :   // If we had left in mobility, it would be exactly the mass flux flowing out of node i.
     165             :   //
     166             :   // This leads to the definition of upwinding:
     167             :   // ***
     168             :   // If _local_re(i) is positive then we use mobility_i.  That is
     169             :   // we use the upwind value of mobility.
     170             :   // ***
     171             :   //
     172             :   // The final subtle thing is we must also conserve fluid mass: the total mass
     173             :   // flowing out of node i must be the sum of the masses flowing
     174             :   // into the other nodes.
     175             : 
     176             :   // FIRST:
     177             :   // this is a dirty way of getting around precision loss problems
     178             :   // and problems at steadystate where upwinding oscillates from
     179             :   // node-to-node causing nonconvergence.
     180             :   // I'm not sure if i actually need to do this in moose.  Certainly
     181             :   // in cosflow it is necessary.
     182             :   // I will code a better algorithm if necessary
     183             :   bool reached_steady = true;
     184       19712 :   for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
     185             :   {
     186       19220 :     if (_local_re(nodenum) >= 1E-20)
     187             :     {
     188             :       reached_steady = false;
     189             :       break;
     190             :     }
     191             :   }
     192             :   reached_steady = false;
     193             : 
     194             :   // DEFINE VARIABLES USED TO ENSURE MASS CONSERVATION
     195             :   // total mass out - used for mass conservation
     196             :   Real total_mass_out = 0;
     197             :   // total flux in
     198             :   Real total_in = 0;
     199             : 
     200             :   // the following holds derivatives of these
     201             :   std::vector<Real> dtotal_mass_out;
     202             :   std::vector<Real> dtotal_in;
     203       12006 :   if (compute_jac)
     204             :   {
     205        6471 :     dtotal_mass_out.assign(_num_nodes, 0);
     206        6471 :     dtotal_in.assign(_num_nodes, 0);
     207             :   }
     208             : 
     209             :   // PERFORM THE UPWINDING!
     210       40398 :   for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
     211             :   {
     212       28392 :     if (_local_re(nodenum) >= 0 || reached_steady) // upstream node
     213             :     {
     214       14326 :       if (compute_jac)
     215             :       {
     216       31062 :         for (_j = 0; _j < _phi.size(); _j++)
     217       23448 :           _local_ke(nodenum, _j) *= _mobility[nodenum];
     218        7614 :         if (jvar == _var.number())
     219             :           // deriv wrt P
     220        3842 :           _local_ke(nodenum, nodenum) += _dmobility_dp[nodenum] * _local_re(nodenum);
     221             :         else
     222             :           // deriv wrt S
     223        3772 :           _local_ke(nodenum, nodenum) += _dmobility_ds[nodenum] * _local_re(nodenum);
     224       31062 :         for (_j = 0; _j < _phi.size(); _j++)
     225       23448 :           dtotal_mass_out[_j] += _local_ke(nodenum, _j);
     226             :       }
     227       14326 :       _local_re(nodenum) *= _mobility[nodenum];
     228       14326 :       total_mass_out += _local_re(nodenum);
     229             :     }
     230             :     else
     231             :     {
     232       14066 :       total_in -= _local_re(nodenum); // note the -= means the result is positive
     233       14066 :       if (compute_jac)
     234       29854 :         for (_j = 0; _j < _phi.size(); _j++)
     235       22392 :           dtotal_in[_j] -= _local_ke(nodenum, _j);
     236             :     }
     237             :   }
     238             : 
     239             :   // CONSERVE MASS
     240             :   // proportion the total_mass_out mass to the inflow nodes, weighting by their _local_re values
     241             :   if (!reached_steady)
     242       40398 :     for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
     243       28392 :       if (_local_re(nodenum) < 0)
     244             :       {
     245       14066 :         if (compute_jac)
     246       29854 :           for (_j = 0; _j < _phi.size(); _j++)
     247             :           {
     248       22392 :             _local_ke(nodenum, _j) *= total_mass_out / total_in;
     249       22392 :             _local_ke(nodenum, _j) +=
     250       22392 :                 _local_re(nodenum) * (dtotal_mass_out[_j] / total_in -
     251       22392 :                                       dtotal_in[_j] * total_mass_out / total_in / total_in);
     252             :           }
     253       14066 :         _local_re(nodenum) *= total_mass_out / total_in;
     254             :       }
     255             : 
     256             :   // ADD RESULTS TO RESIDUAL OR JACOBIAN
     257       12006 :   if (compute_res)
     258             :   {
     259        5535 :     accumulateTaggedLocalResidual();
     260        5535 :     if (_has_save_in)
     261         602 :       for (unsigned int i = 0; i < _save_in.size(); i++)
     262         301 :         _save_in[i]->sys().solution().add_vector(_local_re, _save_in[i]->dofIndices());
     263             :   }
     264             : 
     265       12006 :   if (compute_jac)
     266             :   {
     267        6471 :     accumulateTaggedLocalMatrix();
     268        6471 :     if (_has_diag_save_in && jvar == _var.number())
     269             :     {
     270             :       const unsigned int rows = _local_ke.m();
     271         103 :       DenseVector<Number> diag(rows);
     272         515 :       for (unsigned int i = 0; i < rows; i++)
     273         412 :         diag(i) = _local_ke(i, i);
     274             : 
     275         206 :       for (unsigned int i = 0; i < _diag_save_in.size(); i++)
     276         103 :         _diag_save_in[i]->sys().solution().add_vector(diag, _diag_save_in[i]->dofIndices());
     277             :     }
     278             :   }
     279       12006 : }

Generated by: LCOV version 1.14