https://mooseframework.inl.gov
PorousFlowMassFractionAqueousEquilibriumChemistry.C
Go to the documentation of this file.
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 
11 
13 
16 {
18  params.addRequiredCoupledVar(
19  "mass_fraction_vars",
20  "List of variables that represent the mass fractions. For the aqueous phase these are "
21  "concentrations of the primary species with units m^{3}(chemical)/m^{3}(fluid phase). For "
22  "the other phases (if any) these will typically be initialised to zero and will not change "
23  "throughout the simulation. Format is 'f_ph0^c0 f_ph0^c1 f_ph0^c2 ... f_ph0^c(N-2) f_ph1^c0 "
24  "f_ph1^c1 fph1^c2 ... fph1^c(N-2) ... fphP^c0 f_phP^c1 fphP^c2 ... fphP^c(N-2)' where "
25  "N=number of primary species and P=num_phases, and it is assumed that "
26  "f_ph^c(N-1)=1-sum(f_ph^c,{c,0,N-2}) so that f_ph^c(N-1) need not be given.");
27  params.addRequiredParam<unsigned>("num_reactions",
28  "Number of equations in the system of chemical reactions");
29  params.addParam<bool>("equilibrium_constants_as_log10",
30  false,
31  "If true, the equilibrium constants are written in their log10 form, eg, "
32  "-2. If false, the equilibrium constants are written in absolute terms, "
33  "eg, 0.01");
34  params.addRequiredCoupledVar("equilibrium_constants",
35  "Equilibrium constant for each equation (dimensionless). If these "
36  "are temperature dependent AuxVariables, the Jacobian will not be "
37  "exact");
38  params.addRequiredParam<std::vector<Real>>(
39  "primary_activity_coefficients",
40  "Activity coefficients for the primary species (dimensionless) (one for each)");
41  params.addRequiredParam<std::vector<Real>>(
42  "reactions",
43  "A matrix defining the aqueous reactions. The matrix is entered as a long vector: the first "
44  "row is "
45  "entered first, followed by the second row, etc. There should be num_reactions rows. All "
46  "primary species should appear only on the LHS of each reaction (and there should be just "
47  "one secondary species on the RHS, by definition) so they may have negative coefficients. "
48  "Each row should have number of primary_concentrations entries, which are the stoichiometric "
49  "coefficients. The first coefficient must always correspond to the first primary species, "
50  "etc");
51  params.addRequiredParam<std::vector<Real>>(
52  "secondary_activity_coefficients",
53  "Activity coefficients for the secondary species (dimensionless) (one for each reaction)");
54  params.addPrivateParam<std::string>("pf_material_type", "mass_fraction");
55  params.addClassDescription(
56  "This Material forms a std::vector<std::vector ...> of mass-fractions "
57  "(total concentrations of primary species (m^{3}(primary species)/m^{3}(solution)) and since "
58  "this is for an aqueous system only, mass-fraction equals volume-fraction) corresponding to "
59  "an "
60  "aqueous equilibrium chemistry system. The first mass fraction is the "
61  "concentration of the first primary species, etc, and the last mass "
62  "fraction is the concentration of H2O.");
63  return params;
64 }
65 
68  : PorousFlowMassFraction(parameters),
69  _sec_conc(_nodal_material
70  ? declareProperty<std::vector<Real>>("PorousFlow_secondary_concentration_nodal")
71  : declareProperty<std::vector<Real>>("PorousFlow_secondary_concentration_qp")),
72  _dsec_conc_dvar(_nodal_material ? declareProperty<std::vector<std::vector<Real>>>(
73  "dPorousFlow_secondary_concentration_nodal_dvar")
74  : declareProperty<std::vector<std::vector<Real>>>(
75  "dPorousFlow_secondary_concentration_qp_dvar")),
76 
77  _temperature(_nodal_material ? getMaterialProperty<Real>("PorousFlow_temperature_nodal")
78  : getMaterialProperty<Real>("PorousFlow_temperature_qp")),
79  _dtemperature_dvar(
80  _nodal_material
81  ? getMaterialProperty<std::vector<Real>>("dPorousFlow_temperature_nodal_dvar")
82  : getMaterialProperty<std::vector<Real>>("dPorousFlow_temperature_qp_dvar")),
83 
84  _num_primary(_num_components - 1),
85  _aq_ph(_dictator.aqueousPhaseNumber()),
86  _aq_i(_aq_ph * _num_primary),
87  _num_reactions(getParam<unsigned>("num_reactions")),
88  _equilibrium_constants_as_log10(getParam<bool>("equilibrium_constants_as_log10")),
89  _num_equilibrium_constants(coupledComponents("equilibrium_constants")),
90  _equilibrium_constants(_num_equilibrium_constants),
91  _primary_activity_coefficients(getParam<std::vector<Real>>("primary_activity_coefficients")),
92  _reactions(getParam<std::vector<Real>>("reactions")),
93  _secondary_activity_coefficients(getParam<std::vector<Real>>("secondary_activity_coefficients"))
94 {
95  if (_dictator.numPhases() < 1)
96  mooseError("PorousFlowMassFractionAqueousEquilibriumChemistry: The number of fluid phases must "
97  "not be zero");
98 
99  // correct number of equilibrium constants
101  mooseError("PorousFlowMassFractionAqueousEquilibriumChemistry: The number of "
102  "equilibrium constants is ",
104  " which must be equal to the number of reactions (",
106  ")");
107 
108  // correct number of activity coefficients
110  mooseError("PorousFlowMassFractionAqueousEquilibriumChemistry: The number of primary activity "
111  "coefficients is ",
113  " which must be equal to the number of primary species (",
114  _num_primary,
115  ")");
116 
117  // correct number of stoichiometry coefficients
118  if (_reactions.size() != _num_reactions * _num_primary)
119  mooseError("PorousFlowMassFractionAqueousEquilibriumChemistry: The number of stoichiometric "
120  "coefficients specified in 'reactions' (",
121  _reactions.size(),
122  ") must be equal to the number of reactions (",
124  ") multiplied by the number of primary species (",
125  _num_primary,
126  ")");
127 
128  // correct number of secondary activity coefficients
130  mooseError(
131  "PorousFlowMassFractionAqueousEquilibriumChemistry: The number of secondary activity "
132  "coefficients is ",
134  " which must be equal to the number of secondary species (",
136  ")");
137 
138  // correct number of reactions
139  if (_num_reactions != _dictator.numAqueousEquilibrium())
140  mooseError("PorousFlowMassFractionAqueousEquilibriumChemistry: You have specified the number "
141  "of reactions to be ",
143  " but the Dictator knows that the number of aqueous equilibrium reactions is ",
144  _dictator.numAqueousEquilibrium());
145 
146  for (unsigned i = 0; i < _num_equilibrium_constants; ++i)
147  {
148  // If equilibrium_constants are elemental AuxVariables (or constants), we want to use
149  // coupledGenericValue() rather than coupledGenericDofValue()
150  const bool is_nodal = isCoupled("equilibrium_constants")
151  ? getFieldVar("equilibrium_constants", i)->isNodal()
152  : false;
153 
155  (_nodal_material && is_nodal ? &coupledDofValues("equilibrium_constants", i)
156  : &coupledValue("equilibrium_constants", i));
157  }
158 }
159 
160 void
162 {
164 }
165 
166 void
168 {
169  // size all properties correctly and populate the non-aqueous phase info
171 
172  // size the secondary concentrations
175  for (unsigned r = 0; r < _num_reactions; ++r)
176  _dsec_conc_dvar[_qp][r].assign(_num_var, 0.0);
177 
178  // Compute the secondary concentrations
179  if (_t_step == 0 && !_app.isRestarting())
181  else
183 
184  // compute _mass_frac[_qp][_aq_ph]
185  _mass_frac[_qp][_aq_ph][_num_components - 1] = 1.0; // the final component is H20
186  for (unsigned i = 0; i < _num_primary; ++i)
187  {
188  _mass_frac[_qp][_aq_ph][i] = (*_mf_vars[_aq_i + i])[_qp];
189  for (unsigned r = 0; r < _num_reactions; ++r)
190  _mass_frac[_qp][_aq_ph][i] += stoichiometry(r, i) * _sec_conc[_qp][r];
191 
192  // remove mass-fraction from the H20 component
193  _mass_frac[_qp][_aq_ph][_num_components - 1] -= _mass_frac[_qp][_aq_ph][i];
194  }
195 
196  // Compute the derivatives of the secondary concentrations
197  std::vector<std::vector<Real>> dsec(_num_reactions);
198  std::vector<Real> dsec_dT(_num_reactions);
199  for (unsigned r = 0; r < _num_reactions; ++r)
200  {
202  dsec_dT[r] = dQpSecondaryConcentration_dT(r);
203  }
204 
205  // Compute the derivatives of the mass_frac wrt the primary concentrations
206  // This is used in _dmass_frac_dvar as well as _grad_mass_frac
207  std::vector<std::vector<Real>> dmf(_num_components);
208  for (unsigned i = 0; i < _num_components; ++i)
209  dmf[i].assign(_num_primary, 0.0);
210  for (unsigned wrt = 0; wrt < _num_primary; ++wrt)
211  {
212  // run through the mass fractions (except the last one) adding to their derivatives
213  // The special case is:
214  dmf[wrt][wrt] = 1.0;
215  // The secondary-species contributions are:
216  for (unsigned i = 0; i < _num_primary; ++i)
217  for (unsigned r = 0; r < _num_reactions; ++r)
218  dmf[i][wrt] += stoichiometry(r, i) * dsec[r][wrt];
219 
220  // compute dmf[_num_components - 1]
221  for (unsigned i = 0; i < _num_primary; ++i)
222  dmf[_num_components - 1][wrt] -= dmf[i][wrt];
223  }
224 
225  // Compute the derivatives of the mass_frac wrt the temperature
226  // This is used in _dmass_frac_dvar
227  std::vector<Real> dmf_dT(_num_components, 0.0);
228  for (unsigned i = 0; i < _num_components - 1; ++i)
229  {
230  for (unsigned r = 0; r < _num_reactions; ++r)
231  dmf_dT[i] += stoichiometry(r, i) * dsec_dT[r];
232  dmf_dT[_num_components - 1] -= dmf_dT[i];
233  }
234 
235  // compute _dmass_frac_dvar[_qp][_aq_ph] and _dsec_conc_dvar[_qp]
236  for (unsigned wrt = 0; wrt < _num_primary; ++wrt)
237  {
238  // derivative with respect to the "wrt"^th primary species concentration
239  if (!_dictator.isPorousFlowVariable(_mf_vars_num[_aq_i + wrt]))
240  continue;
241  const unsigned pf_wrt = _dictator.porousFlowVariableNum(_mf_vars_num[_aq_i + wrt]);
242 
243  // run through the mass fractions, building the derivative using dmf
244  for (unsigned i = 0; i < _num_components; ++i)
245  (*_dmass_frac_dvar)[_qp][_aq_ph][i][pf_wrt] = dmf[i][wrt];
246 
247  // run through the secondary concentrations, using dsec in the appropriate places
248  for (unsigned r = 0; r < _num_reactions; ++r)
249  _dsec_conc_dvar[_qp][r][pf_wrt] = dsec[r][wrt];
250  }
251 
252  // use the derivative wrt temperature
253  for (unsigned i = 0; i < _num_components; ++i)
254  for (unsigned v = 0; v < _num_var; ++v)
255  (*_dmass_frac_dvar)[_qp][_aq_ph][i][v] += dmf_dT[i] * _dtemperature_dvar[_qp][v];
256  for (unsigned r = 0; r < _num_reactions; ++r)
257  for (unsigned v = 0; v < _num_var; ++v)
258  _dsec_conc_dvar[_qp][r][v] += dsec_dT[r] * _dtemperature_dvar[_qp][v];
259 
260  // compute the gradient, if needed
261  // NOTE: The derivative d(grad_mass_frac)/d(var) != d(mass_frac)/d(var) * grad_phi
262  // because mass fraction is a nonlinear function of the primary variables
263  // This means that the Jacobian in PorousFlowDispersiveFlux will be wrong
264  if (!_nodal_material)
265  {
266  (*_grad_mass_frac)[_qp][_aq_ph][_num_components - 1] = 0.0;
267  for (unsigned comp = 0; comp < _num_components - 1; ++comp)
268  {
269  (*_grad_mass_frac)[_qp][_aq_ph][comp] = 0.0;
270  for (unsigned wrt = 0; wrt < _num_primary; ++wrt)
271  (*_grad_mass_frac)[_qp][_aq_ph][comp] +=
272  dmf[comp][wrt] * (*_grad_mf_vars[_aq_i + wrt])[_qp];
273  (*_grad_mass_frac)[_qp][_aq_ph][_num_components - 1] -= (*_grad_mass_frac)[_qp][_aq_ph][comp];
274  }
275  }
276 }
277 
278 Real
280  unsigned primary_num) const
281 {
282  const unsigned index = reaction_num * _num_primary + primary_num;
283  return _reactions[index];
284 }
285 
286 void
288  unsigned & zero_conc_index, unsigned & zero_count) const
289 {
290  zero_count = 0;
291  for (unsigned i = 0; i < _num_primary; ++i)
292  {
293  if (_primary_activity_coefficients[i] * (*_mf_vars[_aq_i + i])[_qp] <= 0.0)
294  {
295  zero_count += 1;
296  zero_conc_index = i;
297  if (zero_count > 1)
298  return;
299  }
300  }
301  return;
302 }
303 
304 void
306 {
307  _sec_conc[_qp].assign(_num_reactions, 0.0);
308 }
309 
310 void
312 {
313  for (unsigned r = 0; r < _num_reactions; ++r)
314  {
315  _sec_conc[_qp][r] = 1.0;
316  for (unsigned i = 0; i < _num_primary; ++i)
317  {
318  const Real gamp = _primary_activity_coefficients[i] * (*_mf_vars[_aq_i + i])[_qp];
319  if (gamp <= 0.0)
320  {
321  if (stoichiometry(r, i) < 0.0)
322  _sec_conc[_qp][r] = std::numeric_limits<Real>::max();
323  else if (stoichiometry(r, i) == 0.0)
324  _sec_conc[_qp][r] *= 1.0;
325  else
326  {
327  _sec_conc[_qp][r] = 0.0;
328  break;
329  }
330  }
331  else
332  _sec_conc[_qp][r] *= std::pow(gamp, stoichiometry(r, i));
333  }
334  _sec_conc[_qp][r] *=
336  : (*_equilibrium_constants[r])[_qp]);
338  }
339 }
340 
341 void
343  unsigned reaction_num, std::vector<Real> & dsc) const
344 {
345  dsc.assign(_num_primary, 0.0);
346 
347  /*
348  * the derivatives are straightforward if all primary > 0.
349  *
350  * If more than one primary = 0 then I set the derivatives to zero, even though it could be argued
351  * that with certain stoichiometric coefficients you might have derivative = 0/0 and it might be
352  * appropriate to set this to a non-zero finite value.
353  *
354  * If exactly one primary = 0 and its stoichiometry = 1 then the derivative wrt this one is
355  * nonzero.
356  * If exactly one primary = 0 and its stoichiometry > 1 then all derivatives are zero.
357  * If exactly one primary = 0 and its stoichiometry < 1 then the derivative wrt this one is
358  * infinity
359  */
360 
361  unsigned zero_count = 0;
362  unsigned zero_conc_index = 0;
363  findZeroConcentration(zero_conc_index, zero_count);
364 
365  if (zero_count == 0)
366  {
367  for (unsigned i = 0; i < _num_primary; ++i)
368  dsc[i] = stoichiometry(reaction_num, i) * _sec_conc[_qp][reaction_num] /
369  (*_mf_vars[_aq_i + i])[_qp];
370  }
371  else
372  {
373  // count the number of primary <= 0, and record the one that's zero
374  if (zero_count == 1 and stoichiometry(reaction_num, zero_conc_index) == 1.0)
375  {
376  Real conc_without_zero = 1.0;
377  for (unsigned i = 0; i < _num_primary; ++i)
378  {
379  if (i == zero_conc_index)
380  conc_without_zero *= _primary_activity_coefficients[i];
381  else
382  conc_without_zero *=
384  stoichiometry(reaction_num, i));
385  }
386  conc_without_zero *= (_equilibrium_constants_as_log10
387  ? std::pow(10.0, (*_equilibrium_constants[reaction_num])[_qp])
388  : (*_equilibrium_constants[reaction_num])[_qp]);
389  conc_without_zero /= _secondary_activity_coefficients[reaction_num];
390  dsc[zero_conc_index] = conc_without_zero;
391  }
392  else if (zero_count == 1 && stoichiometry(reaction_num, zero_conc_index) < 1.0)
393  dsc[zero_conc_index] = std::numeric_limits<Real>::max();
394 
395  // all other cases have dsc = 0
396  }
397 }
398 
399 Real
401  unsigned /* reaction_num */) const
402 {
403  return 0.0;
404 }
const bool _equilibrium_constants_as_log10
Whether the equilibium constants are written in their log10 form, or in absolute terms.
const unsigned int _num_reactions
Number of equations in the aqueous geochemistry system.
Material designed to form a std::vector<std::vector> of mass fractions from the individual mass fract...
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
MaterialProperty< std::vector< std::vector< std::vector< Real > > > > *const _dmass_frac_dvar
Derivative of the mass fraction matrix with respect to the porous flow variables. ...
void addPrivateParam(const std::string &name, const T &value)
void mooseError(Args &&... args)
std::vector< unsigned int > _mf_vars_num
The variable number of the mass-fraction variables.
const std::vector< Real > _reactions
Stoichiometry defining the aqeuous geochemistry equilibrium reactions.
MaterialProperty< std::vector< Real > > & _sec_conc
Secondary concentrations at quadpoint or nodes.
virtual void computeQpSecondaryConcentrations()
Compute the secondary-species concentration as defined by the chemistry Must be overridden by derived...
const std::vector< Real > _primary_activity_coefficients
Activity coefficients for the primary species (dimensionless)
virtual void computeQpProperties() override
void addRequiredParam(const std::string &name, const std::string &doc_string)
virtual void resize(const std::size_t size) override final
virtual Real dQpSecondaryConcentration_dT(unsigned reaction_num) const
Computes derivative of the secondary concentration with respect to the temperature Must be overridden...
void findZeroConcentration(unsigned &zero_conc_index, unsigned &zero_count) const
Checks gamp[i] = _primary_activity_coefficients[i] * (*_primary[i])[qp].
Real stoichiometry(unsigned reaction_num, unsigned primary_num) const
The stoichiometric coefficient.
const MaterialProperty< std::vector< Real > > & _dtemperature_dvar
d(temperature)/(d porflow variable)
const unsigned int _num_components
Number of fluid components.
GenericMaterialProperty< std::vector< std::vector< RealGradient > >, is_ad > *const _grad_mass_frac
Gradient of the mass fraction matrix at the quad points.
GenericMaterialProperty< std::vector< std::vector< Real > >, is_ad > & _mass_frac
Mass fraction matrix at quadpoint or nodes.
std::vector< const GenericVariableGradient< is_ad > * > _grad_mf_vars
The gradient of the mass-fraction variables.
virtual void dQpSecondaryConcentration_dprimary(unsigned reaction_num, std::vector< Real > &dsc) const
Computes derivative of the secondary concentration with respect to the primary concentrations Must be...
const unsigned int _num_var
Number of PorousFlow variables.
const unsigned int _aq_i
Index (into _mf_vars) of the first of the primary species.
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
static const std::string v
Definition: NS.h:84
virtual void initQpSecondaryConcentrations()
Initialises (at _t_step = 0) the secondary concentrations.
const unsigned _num_equilibrium_constants
Number of equilibrium_constants provided.
void addClassDescription(const std::string &doc_string)
std::vector< const GenericVariableValue< is_ad > * > _mf_vars
The mass-fraction variables.
Material designed to form a std::vector<std::vector> of mass fractions from primary-species concentra...
std::vector< const VariableValue * > _equilibrium_constants
Equilibrium constants (dimensionless)
MooseUnits pow(const MooseUnits &, int)
registerMooseObject("PorousFlowApp", PorousFlowMassFractionAqueousEquilibriumChemistry)
MaterialProperty< std::vector< std::vector< Real > > > & _dsec_conc_dvar
Derivative of the secondary concentrations with respect to the porous flow variables.
const std::vector< Real > _secondary_activity_coefficients
Activity coefficients for the secondary species.