https://mooseframework.inl.gov
Functions
GeochemistryKineticRateCalculator Namespace Reference

Provides a parametric description of a general kinetic rate. More...

Functions

void calculateRate (const std::vector< Real > &promoting_indices, const std::vector< Real > &promoting_monod_indices, const std::vector< Real > &promoting_half_saturation, const KineticRateUserDescription &description, const std::vector< std::string > &basis_species_name, const std::vector< bool > &basis_species_gas, const std::vector< Real > &basis_molality, const std::vector< Real > &basis_activity, const std::vector< bool > &basis_activity_known, const std::vector< std::string > &eqm_species_name, const std::vector< bool > &eqm_species_gas, const std::vector< Real > &eqm_molality, const std::vector< Real > &eqm_activity, const DenseMatrix< Real > &eqm_stoichiometry, Real kin_moles, Real kin_species_molecular_weight, Real log10K, Real log10_activity_product, const DenseMatrix< Real > &kin_stoichiometry, unsigned kin, Real temp_degC, Real &rate, Real &drate_dkin, std::vector< Real > &drate_dmol)
 Calclates a kinetic rate and its derivative. More...
 

Detailed Description

Provides a parametric description of a general kinetic rate.

This is a separate class with very little functionality partly so that it can be used by multiple other classes (PertinentGeochemicalSystem, GeochemistryKineticRate) but also so that it can be easily added to if more general kinetic rates are ever required.

The rate is a product of the following terms:

intrinsic_rate_constant area_quantity mass of the kinetic_species, measured in grams, if multiply_by_mass is true (molality of kinetic_species)^kinetic_molal_index 1 / ((molality of kinetic_species)^kinetic_molal_index + kinetic_half_saturation^kinetic_molal_index)^kinetic_monod_index product over the promoting_species of m^(promoting_index) / (m^(promoting_index) + half_saturation^(promoting_index))^(promoting_monod_index) |1 - (Q/K)^theta|^eta, where K = eqm_constant_in_database * exp(- energy_captured / R / TK) exp(activation_energy / R * (1/T0 - 1/T)) D(1 - (Q/K))

Some explanation may be useful:

intrinsic_rate_constant * area_quantity * [mass of kinetic_species] has units mol.s^-1. This allows for the following common possibilities:

The "m" in the promoting species product is:

Q is the activity product, defined by the kinetic_species reaction (defined in the database file). K is the reaction's equilibrium constant (defined in the database file) multiplied by exp(-energy_captured / (RT)). R = 8.314472 m^2.kg.s^-2.K^-1.mol^-1 = 8.314472 J.K^-1.mol^-1 is the gas constant. T is the temperature in Kelvin. T0 is a reference temperature, in Kelvin. It is inputted as 1/T0 so that 1/T0 = 0 is possible.

D(x) depends on direction. If direction == BOTH then D(x) = sgn(x). If direction == DISSOLUTION then D(x) = (x>0)?1:0. If direction == PRECIPITATION then D(x) = (x<0)?-1:0. If direction == RAW then D=1. If direction == DEATH then D=1.

The amount of kinetic species that is generated is kinetic_bio_efficiency * rate. Note that rate

0 for dissolution of the kinetic species, so kinetic_bio_efficiency defaults to -1. However,

for biogeochemistry, it is appropriate to set kinetic_bio_efficiency > 0, so that "dissolution" of the biomass (with rate > 0) generates further biomass

Function Documentation

◆ calculateRate()

void GeochemistryKineticRateCalculator::calculateRate ( const std::vector< Real > &  promoting_indices,
const std::vector< Real > &  promoting_monod_indices,
const std::vector< Real > &  promoting_half_saturation,
const KineticRateUserDescription description,
const std::vector< std::string > &  basis_species_name,
const std::vector< bool > &  basis_species_gas,
const std::vector< Real > &  basis_molality,
const std::vector< Real > &  basis_activity,
const std::vector< bool > &  basis_activity_known,
const std::vector< std::string > &  eqm_species_name,
const std::vector< bool > &  eqm_species_gas,
const std::vector< Real > &  eqm_molality,
const std::vector< Real > &  eqm_activity,
const DenseMatrix< Real > &  eqm_stoichiometry,
Real  kin_moles,
Real  kin_species_molecular_weight,
Real  log10K,
Real  log10_activity_product,
const DenseMatrix< Real > &  kin_stoichiometry,
unsigned  kin,
Real  temp_degC,
Real rate,
Real drate_dkin,
std::vector< Real > &  drate_dmol 
)

Calclates a kinetic rate and its derivative.

The rate is a product of the following terms:

intrinsic_rate_constant area_quantity mass of the kinetic_species, measured in grams, if multiply_by_mass is true product over the promoting_species of m^(promoting_index) |1 - (Q/K)^theta|^eta exp(activation_energy / R * (1/T0 - 1/T)) D(1 - (Q/K))

Parameters
promoting_indicesof the basis species and equilibrium species. Note that this is different from description.promoting_indices (which is paired with description.promoting_species) because it is the promoting indices for the current basis and equilibrium. For computational efficiency promoting_indices[0:num_basis-1] are the promoting indices for the current basis species while promoting_indices[num_basis:] are the promoting indices for the current eqm species
descriptioncontains definition of intrinsic_rate_constant, area_quantity, etc
basis_species_namevector of basis species names
basis_species_gasi^th element is true if the i^th basis species is a gas
basis_molalityvector of the basis molalities (zeroth element is kg of solvent water, and this is mole number for mineral species)
basis_activityvector of basis activities
basis_activity_knowni^th element is true if the activity for the i^th basis species has been constrained by the user
eqm_species_namevector of eqm species names
eqm_species_gasi^th element is true if the i^th eqm species is a gas
eqm_molalityvector of the eqm molalities (zeroth element is kg of solvent water, and this is mole number for mineral species)
eqm_activityvector of eqm activities
eqm_stoichiometrymatrix of stoichiometric coefficient
kin_molesmoles of the kinetic species
kin_species_molecular_weightmolecular weight (g/mol) of the kinetic species
kin_stoichiometrykinetic stoichiometry matrix (only row=kin is used)
kinthe row of kin_stoichiometry that corresponds to the kinetic species
log10Klog10(equilibrium constant) of the kinetic species
log10_activity_productlog10(activity product) of the kinetic species
temp_degtemperature in degC
[out]ratethe rate computed by this method
[out]drate_dkind(rate)/d(mole number of the kinetic species)
[out]drate_dmold(rate)/d(basis molality[i])

Definition at line 15 of file GeochemistryKineticRateCalculator.C.

Referenced by GeochemicalSystem::addKineticRates(), and TEST().

39 {
40  const unsigned num_basis = basis_species_name.size();
41  const unsigned num_eqm = eqm_species_name.size();
42 
43  if (num_basis + num_eqm != promoting_indices.size())
44  mooseError("kinetic_rate: promoting_indices incorrectly sized ", promoting_indices.size());
45  if (num_basis + num_eqm != promoting_monod_indices.size())
46  mooseError("kinetic_rate: promoting_monod_indices incorrectly sized ",
47  promoting_monod_indices.size());
48  if (num_basis + num_eqm != promoting_half_saturation.size())
49  mooseError("kinetic_rate: promoting_half_saturation incorrectly sized ",
50  promoting_half_saturation.size());
51  if (!(num_basis == basis_species_gas.size() && num_basis == basis_molality.size() &&
52  num_basis == basis_activity.size() && num_basis == basis_activity_known.size() &&
53  num_basis == drate_dmol.size()))
54  mooseError("kinetic_rate: incorrectly sized basis-species vectors ",
55  num_basis,
56  " ",
57  basis_species_gas.size(),
58  " ",
59  basis_molality.size(),
60  " ",
61  basis_activity.size(),
62  " ",
63  basis_activity_known.size(),
64  " ",
65  drate_dmol.size());
66  if (!(num_eqm == eqm_species_gas.size() && num_eqm == eqm_molality.size() &&
67  num_eqm == eqm_activity.size()))
68  mooseError("kinetic_rate: incorrectly sized equilibrium-species vectors ",
69  num_eqm,
70  " ",
71  eqm_species_gas.size(),
72  " ",
73  eqm_molality.size(),
74  " ",
75  eqm_activity.size());
76  if (!(eqm_stoichiometry.m() == num_eqm && eqm_stoichiometry.n() == num_basis))
77  mooseError("kinetic_rate: incorrectly sized eqm stoichiometry matrix ",
78  eqm_stoichiometry.m(),
79  " ",
80  eqm_stoichiometry.n());
81  if (!(kin_stoichiometry.m() > kin && kin_stoichiometry.n() == num_basis))
82  mooseError("kinetic_rate: incorrectly sized kinetic stoichiometry matrix ",
83  kin_stoichiometry.m(),
84  " ",
85  kin_stoichiometry.n());
86 
87  rate = description.intrinsic_rate_constant;
88  rate *= description.area_quantity;
89  if (description.multiply_by_mass)
90  rate *= kin_moles * kin_species_molecular_weight;
91 
92  const Real kin_molality = kin_moles / basis_molality[0];
93  const Real dkin_molality_dkin_moles = 1.0 / basis_molality[0];
94  const Real dkin_molality_dnw = -kin_molality / basis_molality[0];
95  if (description.kinetic_molal_index != 0.0)
96  rate *= std::pow(kin_molality, description.kinetic_molal_index);
97  if (description.kinetic_monod_index != 0.0)
98  rate /=
99  std::pow(std::pow(kin_molality, description.kinetic_molal_index) +
100  std::pow(description.kinetic_half_saturation, description.kinetic_molal_index),
101  description.kinetic_monod_index);
102 
103  // promoting species numerators
104  for (unsigned i = 0; i < num_basis; ++i)
105  {
106  if (promoting_indices[i] == 0.0)
107  continue;
108  if (basis_species_gas[i] || basis_species_name[i] == "H+" || basis_species_name[i] == "OH-")
109  rate *= std::pow(basis_activity[i], promoting_indices[i]);
110  else
111  rate *= std::pow(basis_molality[i], promoting_indices[i]);
112  }
113  for (unsigned j = 0; j < num_eqm; ++j)
114  {
115  const unsigned index = num_basis + j;
116  if (promoting_indices[index] == 0.0)
117  continue;
118  if (eqm_species_gas[j] || eqm_species_name[j] == "H+" || eqm_species_name[j] == "OH-")
119  rate *= std::pow(eqm_activity[j], promoting_indices[index]);
120  else
121  rate *= std::pow(eqm_molality[j], promoting_indices[index]);
122  }
123 
124  // promoting species denominators: the monod terms
125  for (unsigned i = 0; i < num_basis; ++i)
126  {
127  if (promoting_monod_indices[i] == 0.0)
128  continue;
129  if (basis_species_gas[i] || basis_species_name[i] == "H+" || basis_species_name[i] == "OH-")
130  rate /= std::pow(std::pow(basis_activity[i], promoting_indices[i]) +
131  std::pow(promoting_half_saturation[i], promoting_indices[i]),
132  promoting_monod_indices[i]);
133  else
134  rate /= std::pow(std::pow(basis_molality[i], promoting_indices[i]) +
135  std::pow(promoting_half_saturation[i], promoting_indices[i]),
136  promoting_monod_indices[i]);
137  }
138  for (unsigned j = 0; j < num_eqm; ++j)
139  {
140  const unsigned index = num_basis + j;
141  if (promoting_monod_indices[index] == 0.0)
142  continue;
143  if (eqm_species_gas[j] || eqm_species_name[j] == "H+" || eqm_species_name[j] == "OH-")
144  rate /= std::pow(std::pow(eqm_activity[j], promoting_indices[index]) +
145  std::pow(promoting_half_saturation[index], promoting_indices[index]),
146  promoting_monod_indices[index]);
147  else
148  rate /= std::pow(std::pow(eqm_molality[j], promoting_indices[index]) +
149  std::pow(promoting_half_saturation[index], promoting_indices[index]),
150  promoting_monod_indices[index]);
151  }
152 
153  // temperature dependence
154  rate *= std::exp(
156  (description.one_over_T0 - 1.0 / (temp_degC + GeochemistryConstants::CELSIUS_TO_KELVIN)));
157 
158  // dependence on activity and equilibrium constant
159  const Real log10K_bio = log10K - description.energy_captured /
163  const Real ap_over_k = std::pow(10.0, log10_activity_product - log10K_bio);
164  const Real theta_term = std::pow(ap_over_k, description.theta);
165  switch (description.direction)
166  {
168  if (ap_over_k > 1.0) // precipitation
169  rate = -rate;
170  // leave the sign of rate unchanged for dissolution
171  break;
173  if (ap_over_k > 1.0) // precipitation
174  rate = 0.0;
175  // leave the sign of rate unchanged for dissolution
176  break;
178  if (ap_over_k > 1.0) // precipitation
179  rate = -rate;
180  else // dissolution
181  rate = 0.0;
182  break;
184  break; // no dependence on 1 - ap_over_k
186  break; // no dependence on 1 - ap_over_k
187  }
188  // const Real rate_no_theta_term = rate; // needed for derivative calcs when theta_term == 1
189  rate *= (description.eta == 0.0)
190  ? 1.0
191  : ((theta_term == 1.0) ? 0.0 : std::pow(std::abs(1.0 - theta_term), description.eta));
192 
193  for (unsigned i = 0; i < num_basis; ++i)
194  drate_dmol[i] = 0.0;
195  drate_dkin = 0.0;
196 
197  // derivatives with respect to the kinetic species moles
198  if (description.multiply_by_mass)
199  drate_dkin += rate / kin_moles;
200 
201  if (description.kinetic_molal_index != 0.0)
202  {
203  const Real d_by_dkin_molality = description.kinetic_molal_index * rate / kin_molality;
204  drate_dkin += d_by_dkin_molality * dkin_molality_dkin_moles;
205  drate_dmol[0] += d_by_dkin_molality * dkin_molality_dnw;
206  }
207  if (!(description.kinetic_molal_index == 0 || description.kinetic_monod_index == 0.0))
208  {
209  const Real d_by_dkin_molality =
210  -description.kinetic_monod_index * description.kinetic_molal_index *
211  std::pow(kin_molality, description.kinetic_molal_index - 1) * rate /
212  (std::pow(kin_molality, description.kinetic_molal_index) +
213  std::pow(description.kinetic_half_saturation, description.kinetic_molal_index));
214  drate_dkin += d_by_dkin_molality * dkin_molality_dkin_moles;
215  drate_dmol[0] += d_by_dkin_molality * dkin_molality_dnw;
216  }
217 
218  // Derivatives of the promoting-species numerators (ignore all derivatives of activity
219  // coefficients), so
220  // d(molality^P) / dmolality = P * molality^P / molality , and
221  // d(activity^P)/d(molality) = P activity^(P-1) d(activity)/d(molality) = P activity^(P-1)
222  // activity_product = P * activity^P / molality
223  for (unsigned i = 0; i < num_basis; ++i)
224  {
225  if (promoting_indices[i] == 0.0)
226  continue;
227  else if (basis_species_gas[i]) // molality is undefined
228  continue;
229  else
230  drate_dmol[i] += promoting_indices[i] * rate / basis_molality[i];
231  }
232  for (unsigned j = 0; j < num_eqm; ++j)
233  {
234  // d(eqm^P)/d(basis_i) = P eqm^P / eqm * d(eqm)/d(basis_i) = P eqm^P * stoi / basis_i
235  const unsigned index = num_basis + j;
236  if (promoting_indices[index] == 0.0)
237  continue;
238  for (unsigned i = 1; i < num_basis; ++i) // deriv of water activity is ignored
239  if (!(basis_species_gas[i] || eqm_stoichiometry(j, i) == 0.0))
240  drate_dmol[i] +=
241  promoting_indices[index] * rate * eqm_stoichiometry(j, i) / basis_molality[i];
242  }
243  // Derivatives of the promoting-species denominators (ignore all derivatives of activity
244  // coefficients) so
245  // d((molality^P + K)^(-u))/d(molality) = -u (molality^P + K)^(-u) / (molality^P + K) * P *
246  // molality^P / molality
247  // d((activity^P + K)^(-u))/d(molality) = -u (activity^P + K)^(-u) /
248  // (activity^P + K) * P * activity^P / molality
249  for (unsigned i = 0; i < num_basis; ++i)
250  {
251  if (promoting_indices[i] == 0.0 || promoting_monod_indices[i] == 0.0)
252  continue;
253  else if (basis_species_gas[i]) // molality is undefined
254  continue;
255  else if (basis_species_name[i] == "H+" || basis_species_name[i] == "OH-")
256  drate_dmol[i] -= promoting_monod_indices[i] * promoting_indices[i] *
257  std::pow(basis_activity[i], promoting_indices[i]) * rate /
258  basis_molality[i] /
259  (std::pow(basis_activity[i], promoting_indices[i]) +
260  std::pow(promoting_half_saturation[i], promoting_indices[i]));
261  else
262  drate_dmol[i] -= promoting_monod_indices[i] * promoting_indices[i] *
263  std::pow(basis_molality[i], promoting_indices[i] - 1) * rate /
264  (std::pow(basis_molality[i], promoting_indices[i]) +
265  std::pow(promoting_half_saturation[i], promoting_indices[i]));
266  }
267  for (unsigned j = 0; j < num_eqm; ++j)
268  {
269  const unsigned index = num_basis + j;
270  if (promoting_indices[index] == 0.0 || promoting_monod_indices[index] == 0.0)
271  continue;
272  for (unsigned i = 1; i < num_basis; ++i) // deriv of water activity is ignored
273  {
274  if (basis_species_gas[i] || eqm_stoichiometry(j, i) == 0.0)
275  continue;
276  else if (eqm_species_gas[j] || eqm_species_name[j] == "H+" || eqm_species_name[j] == "OH-")
277  drate_dmol[i] -= promoting_monod_indices[index] * promoting_indices[index] *
278  std::pow(eqm_activity[j], promoting_indices[index]) * rate *
279  eqm_stoichiometry(j, i) /
280  (std::pow(eqm_activity[j], promoting_indices[index]) +
281  std::pow(promoting_half_saturation[index], promoting_indices[index])) /
282  basis_molality[i];
283  else
284  drate_dmol[i] -= promoting_monod_indices[index] * promoting_indices[index] *
285  std::pow(eqm_molality[j], promoting_indices[index]) * rate *
286  eqm_stoichiometry(j, i) /
287  (std::pow(eqm_molality[j], promoting_indices[index]) +
288  std::pow(promoting_half_saturation[index], promoting_indices[index])) /
289  basis_molality[i];
290  }
291  }
292  // derivative of the activity-product term
293  Real deriv_ap_term = 0.0;
294  if (theta_term != 1.0)
295  deriv_ap_term =
296  description.eta * rate / std::abs(1 - theta_term) * (-description.theta) * theta_term;
297  else // theta_term = 1, so rate = 0 (unless eta = 0 too)
298  {
299  if (description.eta > 1)
300  deriv_ap_term = 0.0;
301  else if (description.eta == 1.0)
302  deriv_ap_term = 0.0; // rate_no_theta_term * description.theta * theta_term;
303  else if (description.eta == 0.0)
304  deriv_ap_term = 0.0;
305  else
306  deriv_ap_term = std::numeric_limits<Real>::max();
307  }
308  if (theta_term > 1.0)
309  deriv_ap_term = -deriv_ap_term;
310  for (unsigned i = 1; i < num_basis; ++i)
311  if (!(basis_species_gas[i] || kin_stoichiometry(kin, i) == 0.0 || deriv_ap_term == 0.0))
312  drate_dmol[i] += deriv_ap_term * kin_stoichiometry(kin, i) / basis_molality[i];
313 }
constexpr Real CELSIUS_TO_KELVIN
void mooseError(Args &&... args)
unsigned int m() const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
unsigned int n() const
MooseUnits pow(const MooseUnits &, int)