www.mooseframework.org
Q2PSaturationFlux.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://www.mooseframework.org
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 "Q2PSaturationFlux.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 
23 
26 {
28  params.addRequiredParam<UserObjectName>(
29  "fluid_density",
30  "A RichardsDensity UserObject that defines the fluid density as a function of pressure.");
31  params.addRequiredCoupledVar("porepressure_variable",
32  "The variable representing the porepressure");
33  params.addRequiredParam<UserObjectName>(
34  "fluid_relperm",
35  "A RichardsRelPerm UserObject (eg RichardsRelPermPowerGas) that defines the "
36  "fluid relative permeability as a function of the saturation Variable.");
37  params.addRequiredParam<Real>("fluid_viscosity", "The fluid dynamic viscosity");
38  params.addClassDescription(
39  "Flux according to Darcy-Richards flow. The Variable of this Kernel must be the saturation");
40  return params;
41 }
42 
44  : Kernel(parameters),
45  _density(getUserObject<RichardsDensity>("fluid_density")),
46  _pp(coupledValue("porepressure_variable")),
47  _grad_pp(coupledGradient("porepressure_variable")),
48  _pp_nodal(coupledDofValues("porepressure_variable")),
49  _pp_var(coupled("porepressure_variable")),
50  _relperm(getUserObject<RichardsRelPerm>("fluid_relperm")),
51  _viscosity(getParam<Real>("fluid_viscosity")),
52  _gravity(getMaterialProperty<RealVectorValue>("gravity")),
53  _permeability(getMaterialProperty<RealTensorValue>("permeability")),
54  _num_nodes(0),
55  _mobility(0),
56  _dmobility_dp(0),
57  _dmobility_ds(0)
58 {
59 }
60 
61 void
63 {
64  _num_nodes = _pp_nodal.size();
65 
66  Real density;
67  Real ddensity_dp;
68  Real relperm;
69  Real drelperm_ds;
70 
71  _mobility.resize(_num_nodes);
72  _dmobility_dp.resize(_num_nodes);
73  _dmobility_ds.resize(_num_nodes);
74 
75  for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
76  {
77  density = _density.density(_pp_nodal[nodenum]); // fluid density at the node
78  ddensity_dp = _density.ddensity(_pp_nodal[nodenum]); // d(fluid density)/dP at the node
79  relperm = _relperm.relperm(
80  _var.dofValues()[nodenum]); // relative permeability of the fluid at node nodenum
81  drelperm_ds = _relperm.drelperm(_var.dofValues()[nodenum]); // d(relperm)/dsat
82 
83  // calculate the mobility and its derivatives wrt P and S
84  _mobility[nodenum] = density * relperm / _viscosity;
85  _dmobility_dp[nodenum] = ddensity_dp * relperm / _viscosity;
86  _dmobility_ds[nodenum] = density * drelperm_ds / _viscosity;
87  }
88 }
89 
90 Real
92 {
93  // note this is not the complete residual:
94  // the upwind mobility parts get added in computeResidual
95  return _grad_test[_i][_qp] *
97 }
98 
99 void
101 {
102  upwind(true, false, 0);
103 }
104 
105 void
107 {
108  upwind(false, true, _var.number());
109 }
110 
111 void
113 {
114  upwind(false, true, jvar);
115 }
116 
117 Real
119 {
120  // this is just the derivative of the flux WITHOUT the upstream mobility terms
121  // Those terms get added in during computeJacobian()
122  if (dvar == _pp_var)
123  return _grad_test[_i][_qp] *
124  (_permeability[_qp] *
126  else
127  return 0;
128 }
129 
130 void
131 Q2PSaturationFlux::upwind(bool compute_res, bool compute_jac, unsigned int jvar)
132 {
133  if (compute_jac && !(jvar == _var.number() || jvar == _pp_var))
134  return;
135 
136  // calculate the mobility values and their derivatives
138 
139  // compute the residual without the mobility terms
140  // Even if we are computing the jacobian we still need this
141  // in order to see which nodes are upwind and which are downwind
143 
144  for (_i = 0; _i < _test.size(); _i++)
145  for (_qp = 0; _qp < _qrule->n_points(); _qp++)
147 
148  if (compute_jac)
149  {
151  for (_i = 0; _i < _test.size(); _i++)
152  for (_j = 0; _j < _phi.size(); _j++)
153  for (_qp = 0; _qp < _qrule->n_points(); _qp++)
154  _local_ke(_i, _j) += _JxW[_qp] * _coord[_qp] * computeQpJac(jvar);
155  }
156 
157  // Now perform the upwinding by multiplying the residuals at the
158  // upstream nodes by their mobilities
159  //
160  // The residual for the kernel is the darcy flux.
161  // This is
162  // R_i = int{mobility*flux_no_mob} = int{mobility*grad(pot)*permeability*grad(test_i)}
163  // for node i. where int is the integral over the element.
164  // However, in fully-upwind, the first step is to take the mobility outside the
165  // integral, which was done in the _local_re calculation above.
166  //
167  // NOTE: Physically _local_re(_i) is a measure of fluid flowing out of node i
168  // If we had left in mobility, it would be exactly the mass flux flowing out of node i.
169  //
170  // This leads to the definition of upwinding:
171  // ***
172  // If _local_re(i) is positive then we use mobility_i. That is
173  // we use the upwind value of mobility.
174  // ***
175  //
176  // The final subtle thing is we must also conserve fluid mass: the total mass
177  // flowing out of node i must be the sum of the masses flowing
178  // into the other nodes.
179 
180  // FIRST:
181  // this is a dirty way of getting around precision loss problems
182  // and problems at steadystate where upwinding oscillates from
183  // node-to-node causing nonconvergence.
184  // I'm not sure if i actually need to do this in moose. Certainly
185  // in cosflow it is necessary.
186  // I will code a better algorithm if necessary
187  bool reached_steady = true;
188  for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
189  {
190  if (_local_re(nodenum) >= 1E-20)
191  {
192  reached_steady = false;
193  break;
194  }
195  }
196  reached_steady = false;
197 
198  // DEFINE VARIABLES USED TO ENSURE MASS CONSERVATION
199  // total mass out - used for mass conservation
200  Real total_mass_out = 0;
201  // total flux in
202  Real total_in = 0;
203 
204  // the following holds derivatives of these
205  std::vector<Real> dtotal_mass_out;
206  std::vector<Real> dtotal_in;
207  if (compute_jac)
208  {
209  dtotal_mass_out.assign(_num_nodes, 0);
210  dtotal_in.assign(_num_nodes, 0);
211  }
212 
213  // PERFORM THE UPWINDING!
214  for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
215  {
216  if (_local_re(nodenum) >= 0 || reached_steady) // upstream node
217  {
218  if (compute_jac)
219  {
220  for (_j = 0; _j < _phi.size(); _j++)
221  _local_ke(nodenum, _j) *= _mobility[nodenum];
222  if (jvar == _var.number())
223  // deriv wrt S
224  _local_ke(nodenum, nodenum) += _dmobility_ds[nodenum] * _local_re(nodenum);
225  else
226  // deriv wrt P
227  _local_ke(nodenum, nodenum) += _dmobility_dp[nodenum] * _local_re(nodenum);
228  for (_j = 0; _j < _phi.size(); _j++)
229  dtotal_mass_out[_j] += _local_ke(nodenum, _j);
230  }
231  _local_re(nodenum) *= _mobility[nodenum];
232  total_mass_out += _local_re(nodenum);
233  }
234  else
235  {
236  total_in -= _local_re(nodenum); // note the -= means the result is positive
237  if (compute_jac)
238  for (_j = 0; _j < _phi.size(); _j++)
239  dtotal_in[_j] -= _local_ke(nodenum, _j);
240  }
241  }
242 
243  // CONSERVE MASS
244  // proportion the total_mass_out mass to the inflow nodes, weighting by their _local_re values
245  if (!reached_steady)
246  for (unsigned int nodenum = 0; nodenum < _num_nodes; ++nodenum)
247  if (_local_re(nodenum) < 0)
248  {
249  if (compute_jac)
250  for (_j = 0; _j < _phi.size(); _j++)
251  {
252  _local_ke(nodenum, _j) *= total_mass_out / total_in;
253  _local_ke(nodenum, _j) +=
254  _local_re(nodenum) * (dtotal_mass_out[_j] / total_in -
255  dtotal_in[_j] * total_mass_out / total_in / total_in);
256  }
257  _local_re(nodenum) *= total_mass_out / total_in;
258  }
259 
260  // ADD RESULTS TO RESIDUAL OR JACOBIAN
261  if (compute_res)
262  {
264 
265  if (_has_save_in)
266  for (unsigned int i = 0; i < _save_in.size(); i++)
267  _save_in[i]->sys().solution().add_vector(_local_re, _save_in[i]->dofIndices());
268  }
269 
270  if (compute_jac)
271  {
273  if (_has_diag_save_in && jvar == _var.number())
274  {
275  const unsigned int rows = _local_ke.m();
276  DenseVector<Number> diag(rows);
277  for (unsigned int i = 0; i < rows; i++)
278  diag(i) = _local_ke(i, i);
279 
280  for (unsigned int i = 0; i < _diag_save_in.size(); i++)
281  _diag_save_in[i]->sys().solution().add_vector(diag, _diag_save_in[i]->dofIndices());
282  }
283  }
284 }
virtual Real computeQpResidual() override
Note that this is not the complete residual for the quadpoint In computeResidual we sum over the quad...
void prepareNodalValues()
calculates the nodal values of mobility, and derivatives thereof
const RichardsRelPerm & _relperm
fluid relative permeability
std::vector< Real > _mobility
nodal values of mobility = density*relperm/viscosity These are multiplied by _flux_no_mob to give the...
virtual void computeOffDiagJacobian(unsigned int jvar) override
this simply calls upwind
static InputParameters validParams()
registerMooseObject("RichardsApp", Q2PSaturationFlux)
void accumulateTaggedLocalResidual()
virtual Real drelperm(Real seff) const =0
derivative of relative permeability wrt effective saturation This must be over-ridden in your derived...
MooseVariable & _var
std::vector< MooseVariableFEBase *> _save_in
virtual Real ddensity(Real p) const =0
derivative of fluid density wrt porepressure This must be over-ridden in derived classes to provide a...
const MooseArray< Real > & _JxW
unsigned int number() const
unsigned int _num_nodes
number of nodes in the element
Base class for Richards relative permeability classes that provide relative permeability as a functio...
std::vector< Real > _dmobility_dp
d(_mobility)/d(porepressure) These are used in the jacobian calculations
Real _viscosity
fluid viscosity
std::vector< Real > _dmobility_ds
d(_mobility)/d(saturation) These are used in the jacobian calculations
void upwind(bool compute_res, bool compute_jac, unsigned int jvar)
Do the upwinding for both the residual and jacobian I&#39;ve put both calculations in the same code to tr...
const VariablePhiGradient & _grad_phi
const VariableGradient & _grad_pp
grad(porepressure) at the quadpoints
static const std::string density
Definition: NS.h:33
const VariableValue & _pp
porepressure at the quadpoints
unsigned int m() const
unsigned int _pp_var
variable number of the porepressure variable
virtual void computeJacobian() override
this simply calls upwind
bool _has_diag_save_in
DenseMatrix< Number > _local_ke
This is a fully upwinded flux Kernel The Variable of this Kernel should be the saturation.
void addRequiredParam(const std::string &name, const std::string &doc_string)
virtual Real density(Real p) const =0
fluid density as a function of porepressure This must be over-ridden in derived classes to provide an...
TensorValue< Real > RealTensorValue
const RichardsDensity & _density
fluid density
const MaterialProperty< RealVectorValue > & _gravity
gravity
const VariableTestValue & _test
Q2PSaturationFlux(const InputParameters &parameters)
std::vector< MooseVariableFEBase *> _diag_save_in
const QBase *const & _qrule
bool _has_save_in
unsigned int _i
virtual Real relperm(Real seff) const =0
relative permeability as a function of effective saturation This must be over-ridden in your derived ...
void accumulateTaggedLocalMatrix()
static InputParameters validParams()
const MooseArray< Real > & _coord
Assembly & _assembly
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
unsigned int _j
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const VariableTestGradient & _grad_test
const VariableValue & _pp_nodal
porepressure at the nodes
const DoFValue & dofValues() const override
virtual void computeResidual() override
This simply calls upwind.
DenseVector< Number > _local_re
void addClassDescription(const std::string &doc_string)
Base class for fluid density as a function of porepressure The functions density, ddensity and d2dens...
Real computeQpJac(unsigned int dvar)
the derivative of the flux without the upstream mobility terms
void prepareVectorTag(Assembly &assembly, unsigned int ivar)
void prepareMatrixTag(Assembly &assembly, unsigned int ivar, unsigned int jvar)
const MaterialProperty< RealTensorValue > & _permeability
permeability
const VariablePhiValue & _phi
unsigned int _qp