LCOV - code coverage report
Current view: top level - src/utils - GeochemistryKineticRateCalculator.C (source / functions) Hit Total Coverage
Test: idaholab/moose geochemistry: 2bf808 Lines: 172 172 100.0 %
Date: 2025-07-17 01:29:12 Functions: 1 1 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 "GeochemistryKineticRateCalculator.h"
      11             : 
      12             : namespace GeochemistryKineticRateCalculator
      13             : {
      14             : void
      15        5963 : calculateRate(const std::vector<Real> & promoting_indices,
      16             :               const std::vector<Real> & promoting_monod_indices,
      17             :               const std::vector<Real> & promoting_half_saturation,
      18             :               const KineticRateUserDescription & description,
      19             :               const std::vector<std::string> & basis_species_name,
      20             :               const std::vector<bool> & basis_species_gas,
      21             :               const std::vector<Real> & basis_molality,
      22             :               const std::vector<Real> & basis_activity,
      23             :               const std::vector<bool> & basis_activity_known,
      24             :               const std::vector<std::string> & eqm_species_name,
      25             :               const std::vector<bool> & eqm_species_gas,
      26             :               const std::vector<Real> & eqm_molality,
      27             :               const std::vector<Real> & eqm_activity,
      28             :               const DenseMatrix<Real> & eqm_stoichiometry,
      29             :               Real kin_moles,
      30             :               Real kin_species_molecular_weight,
      31             :               Real log10K,
      32             :               Real log10_activity_product,
      33             :               const DenseMatrix<Real> & kin_stoichiometry,
      34             :               unsigned kin,
      35             :               Real temp_degC,
      36             :               Real & rate,
      37             :               Real & drate_dkin,
      38             :               std::vector<Real> & drate_dmol)
      39             : {
      40        5963 :   const unsigned num_basis = basis_species_name.size();
      41        5963 :   const unsigned num_eqm = eqm_species_name.size();
      42             : 
      43        5963 :   if (num_basis + num_eqm != promoting_indices.size())
      44           1 :     mooseError("kinetic_rate: promoting_indices incorrectly sized ", promoting_indices.size());
      45        5962 :   if (num_basis + num_eqm != promoting_monod_indices.size())
      46           1 :     mooseError("kinetic_rate: promoting_monod_indices incorrectly sized ",
      47           1 :                promoting_monod_indices.size());
      48        5961 :   if (num_basis + num_eqm != promoting_half_saturation.size())
      49           1 :     mooseError("kinetic_rate: promoting_half_saturation incorrectly sized ",
      50           1 :                promoting_half_saturation.size());
      51       11916 :   if (!(num_basis == basis_species_gas.size() && num_basis == basis_molality.size() &&
      52        5957 :         num_basis == basis_activity.size() && num_basis == basis_activity_known.size() &&
      53             :         num_basis == drate_dmol.size()))
      54           5 :     mooseError("kinetic_rate: incorrectly sized basis-species vectors ",
      55             :                num_basis,
      56             :                " ",
      57           5 :                basis_species_gas.size(),
      58             :                " ",
      59           5 :                basis_molality.size(),
      60             :                " ",
      61           5 :                basis_activity.size(),
      62             :                " ",
      63           5 :                basis_activity_known.size(),
      64             :                " ",
      65           5 :                drate_dmol.size());
      66        5955 :   if (!(num_eqm == eqm_species_gas.size() && num_eqm == eqm_molality.size() &&
      67             :         num_eqm == eqm_activity.size()))
      68           3 :     mooseError("kinetic_rate: incorrectly sized equilibrium-species vectors ",
      69             :                num_eqm,
      70             :                " ",
      71           3 :                eqm_species_gas.size(),
      72             :                " ",
      73           3 :                eqm_molality.size(),
      74             :                " ",
      75           3 :                eqm_activity.size());
      76        5952 :   if (!(eqm_stoichiometry.m() == num_eqm && eqm_stoichiometry.n() == num_basis))
      77           2 :     mooseError("kinetic_rate: incorrectly sized eqm stoichiometry matrix ",
      78           2 :                eqm_stoichiometry.m(),
      79             :                " ",
      80           2 :                eqm_stoichiometry.n());
      81        5950 :   if (!(kin_stoichiometry.m() > kin && kin_stoichiometry.n() == num_basis))
      82           2 :     mooseError("kinetic_rate: incorrectly sized kinetic stoichiometry matrix ",
      83           2 :                kin_stoichiometry.m(),
      84             :                " ",
      85           2 :                kin_stoichiometry.n());
      86             : 
      87        5948 :   rate = description.intrinsic_rate_constant;
      88        5948 :   rate *= description.area_quantity;
      89        5948 :   if (description.multiply_by_mass)
      90        5600 :     rate *= kin_moles * kin_species_molecular_weight;
      91             : 
      92        5948 :   const Real kin_molality = kin_moles / basis_molality[0];
      93        5948 :   const Real dkin_molality_dkin_moles = 1.0 / basis_molality[0];
      94        5948 :   const Real dkin_molality_dnw = -kin_molality / basis_molality[0];
      95        5948 :   if (description.kinetic_molal_index != 0.0)
      96         185 :     rate *= std::pow(kin_molality, description.kinetic_molal_index);
      97        5948 :   if (description.kinetic_monod_index != 0.0)
      98         185 :     rate /=
      99         185 :         std::pow(std::pow(kin_molality, description.kinetic_molal_index) +
     100         185 :                      std::pow(description.kinetic_half_saturation, description.kinetic_molal_index),
     101             :                  description.kinetic_monod_index);
     102             : 
     103             :   // promoting species numerators
     104       52391 :   for (unsigned i = 0; i < num_basis; ++i)
     105             :   {
     106       46443 :     if (promoting_indices[i] == 0.0)
     107       45301 :       continue;
     108        2805 :     if (basis_species_gas[i] || basis_species_name[i] == "H+" || basis_species_name[i] == "OH-")
     109         591 :       rate *= std::pow(basis_activity[i], promoting_indices[i]);
     110             :     else
     111         551 :       rate *= std::pow(basis_molality[i], promoting_indices[i]);
     112             :   }
     113      184871 :   for (unsigned j = 0; j < num_eqm; ++j)
     114             :   {
     115      178923 :     const unsigned index = num_basis + j;
     116      178923 :     if (promoting_indices[index] == 0.0)
     117      175645 :       continue;
     118        7078 :     if (eqm_species_gas[j] || eqm_species_name[j] == "H+" || eqm_species_name[j] == "OH-")
     119        2756 :       rate *= std::pow(eqm_activity[j], promoting_indices[index]);
     120             :     else
     121         522 :       rate *= std::pow(eqm_molality[j], promoting_indices[index]);
     122             :   }
     123             : 
     124             :   // promoting species denominators: the monod terms
     125       52391 :   for (unsigned i = 0; i < num_basis; ++i)
     126             :   {
     127       46443 :     if (promoting_monod_indices[i] == 0.0)
     128       46211 :       continue;
     129         618 :     if (basis_species_gas[i] || basis_species_name[i] == "H+" || basis_species_name[i] == "OH-")
     130          59 :       rate /= std::pow(std::pow(basis_activity[i], promoting_indices[i]) +
     131          59 :                            std::pow(promoting_half_saturation[i], promoting_indices[i]),
     132             :                        promoting_monod_indices[i]);
     133             :     else
     134         173 :       rate /= std::pow(std::pow(basis_molality[i], promoting_indices[i]) +
     135         173 :                            std::pow(promoting_half_saturation[i], promoting_indices[i]),
     136             :                        promoting_monod_indices[i]);
     137             :   }
     138      184871 :   for (unsigned j = 0; j < num_eqm; ++j)
     139             :   {
     140      178923 :     const unsigned index = num_basis + j;
     141      178923 :     if (promoting_monod_indices[index] == 0.0)
     142      178390 :       continue;
     143        1563 :     if (eqm_species_gas[j] || eqm_species_name[j] == "H+" || eqm_species_name[j] == "OH-")
     144          36 :       rate /= std::pow(std::pow(eqm_activity[j], promoting_indices[index]) +
     145          36 :                            std::pow(promoting_half_saturation[index], promoting_indices[index]),
     146             :                        promoting_monod_indices[index]);
     147             :     else
     148         497 :       rate /= std::pow(std::pow(eqm_molality[j], promoting_indices[index]) +
     149         497 :                            std::pow(promoting_half_saturation[index], promoting_indices[index]),
     150             :                        promoting_monod_indices[index]);
     151             :   }
     152             : 
     153             :   // temperature dependence
     154        5948 :   rate *= std::exp(
     155        5948 :       description.activation_energy / GeochemistryConstants::GAS_CONSTANT *
     156        5948 :       (description.one_over_T0 - 1.0 / (temp_degC + GeochemistryConstants::CELSIUS_TO_KELVIN)));
     157             : 
     158             :   // dependence on activity and equilibrium constant
     159        5948 :   const Real log10K_bio = log10K - description.energy_captured /
     160        5948 :                                        GeochemistryConstants::GAS_CONSTANT /
     161        5948 :                                        (temp_degC + GeochemistryConstants::CELSIUS_TO_KELVIN) /
     162             :                                        GeochemistryConstants::LOGTEN;
     163        5948 :   const Real ap_over_k = std::pow(10.0, log10_activity_product - log10K_bio);
     164        5948 :   const Real theta_term = std::pow(ap_over_k, description.theta);
     165        5948 :   switch (description.direction)
     166             :   {
     167        5139 :     case DirectionChoiceEnum::BOTH:
     168        5139 :       if (ap_over_k > 1.0) // precipitation
     169         645 :         rate = -rate;
     170             :       // leave the sign of rate unchanged for dissolution
     171             :       break;
     172         572 :     case DirectionChoiceEnum::DISSOLUTION:
     173         572 :       if (ap_over_k > 1.0) // precipitation
     174           1 :         rate = 0.0;
     175             :       // leave the sign of rate unchanged for dissolution
     176             :       break;
     177           2 :     case DirectionChoiceEnum::PRECIPITATION:
     178           2 :       if (ap_over_k > 1.0) // precipitation
     179           1 :         rate = -rate;
     180             :       else // dissolution
     181           1 :         rate = 0.0;
     182             :       break;
     183             :     case DirectionChoiceEnum::RAW:
     184             :       break; // no dependence on 1 - ap_over_k
     185             :     case DirectionChoiceEnum::DEATH:
     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       11896 :   rate *= (description.eta == 0.0)
     190        5948 :               ? 1.0
     191        5560 :               : ((theta_term == 1.0) ? 0.0 : std::pow(std::abs(1.0 - theta_term), description.eta));
     192             : 
     193       52391 :   for (unsigned i = 0; i < num_basis; ++i)
     194       46443 :     drate_dmol[i] = 0.0;
     195        5948 :   drate_dkin = 0.0;
     196             : 
     197             :   // derivatives with respect to the kinetic species moles
     198        5948 :   if (description.multiply_by_mass)
     199        5600 :     drate_dkin += rate / kin_moles;
     200             : 
     201        5948 :   if (description.kinetic_molal_index != 0.0)
     202             :   {
     203         185 :     const Real d_by_dkin_molality = description.kinetic_molal_index * rate / kin_molality;
     204         185 :     drate_dkin += d_by_dkin_molality * dkin_molality_dkin_moles;
     205         185 :     drate_dmol[0] += d_by_dkin_molality * dkin_molality_dnw;
     206             :   }
     207        5948 :   if (!(description.kinetic_molal_index == 0 || description.kinetic_monod_index == 0.0))
     208             :   {
     209             :     const Real d_by_dkin_molality =
     210         185 :         -description.kinetic_monod_index * description.kinetic_molal_index *
     211         185 :         std::pow(kin_molality, description.kinetic_molal_index - 1) * rate /
     212         185 :         (std::pow(kin_molality, description.kinetic_molal_index) +
     213         185 :          std::pow(description.kinetic_half_saturation, description.kinetic_molal_index));
     214         185 :     drate_dkin += d_by_dkin_molality * dkin_molality_dkin_moles;
     215         185 :     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       52391 :   for (unsigned i = 0; i < num_basis; ++i)
     224             :   {
     225       46443 :     if (promoting_indices[i] == 0.0)
     226       45301 :       continue;
     227        1142 :     else if (basis_species_gas[i]) // molality is undefined
     228          30 :       continue;
     229             :     else
     230        1112 :       drate_dmol[i] += promoting_indices[i] * rate / basis_molality[i];
     231             :   }
     232      184871 :   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      178923 :     const unsigned index = num_basis + j;
     236      178923 :     if (promoting_indices[index] == 0.0)
     237      175645 :       continue;
     238       31568 :     for (unsigned i = 1; i < num_basis; ++i) // deriv of water activity is ignored
     239       28290 :       if (!(basis_species_gas[i] || eqm_stoichiometry(j, i) == 0.0))
     240       19587 :         drate_dmol[i] +=
     241       19587 :             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       52391 :   for (unsigned i = 0; i < num_basis; ++i)
     250             :   {
     251       46443 :     if (promoting_indices[i] == 0.0 || promoting_monod_indices[i] == 0.0)
     252       46211 :       continue;
     253         232 :     else if (basis_species_gas[i]) // molality is undefined
     254          19 :       continue;
     255         386 :     else if (basis_species_name[i] == "H+" || basis_species_name[i] == "OH-")
     256          40 :       drate_dmol[i] -= promoting_monod_indices[i] * promoting_indices[i] *
     257          40 :                        std::pow(basis_activity[i], promoting_indices[i]) * rate /
     258          40 :                        basis_molality[i] /
     259          40 :                        (std::pow(basis_activity[i], promoting_indices[i]) +
     260          40 :                         std::pow(promoting_half_saturation[i], promoting_indices[i]));
     261             :     else
     262         173 :       drate_dmol[i] -= promoting_monod_indices[i] * promoting_indices[i] *
     263         173 :                        std::pow(basis_molality[i], promoting_indices[i] - 1) * rate /
     264         173 :                        (std::pow(basis_molality[i], promoting_indices[i]) +
     265         173 :                         std::pow(promoting_half_saturation[i], promoting_indices[i]));
     266             :   }
     267      184871 :   for (unsigned j = 0; j < num_eqm; ++j)
     268             :   {
     269      178923 :     const unsigned index = num_basis + j;
     270      178923 :     if (promoting_indices[index] == 0.0 || promoting_monod_indices[index] == 0.0)
     271      178390 :       continue;
     272        4293 :     for (unsigned i = 1; i < num_basis; ++i) // deriv of water activity is ignored
     273             :     {
     274        3760 :       if (basis_species_gas[i] || eqm_stoichiometry(j, i) == 0.0)
     275        2892 :         continue;
     276        2568 :       else if (eqm_species_gas[j] || eqm_species_name[j] == "H+" || eqm_species_name[j] == "OH-")
     277          36 :         drate_dmol[i] -= promoting_monod_indices[index] * promoting_indices[index] *
     278          36 :                          std::pow(eqm_activity[j], promoting_indices[index]) * rate *
     279          36 :                          eqm_stoichiometry(j, i) /
     280          36 :                          (std::pow(eqm_activity[j], promoting_indices[index]) +
     281          36 :                           std::pow(promoting_half_saturation[index], promoting_indices[index])) /
     282             :                          basis_molality[i];
     283             :       else
     284         832 :         drate_dmol[i] -= promoting_monod_indices[index] * promoting_indices[index] *
     285         832 :                          std::pow(eqm_molality[j], promoting_indices[index]) * rate *
     286         832 :                          eqm_stoichiometry(j, i) /
     287         832 :                          (std::pow(eqm_molality[j], promoting_indices[index]) +
     288         832 :                           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        5948 :   if (theta_term != 1.0)
     295        5943 :     deriv_ap_term =
     296        5943 :         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           5 :     if (description.eta > 1)
     300             :       deriv_ap_term = 0.0;
     301           4 :     else if (description.eta == 1.0)
     302             :       deriv_ap_term = 0.0; // rate_no_theta_term * description.theta * theta_term;
     303           2 :     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        5948 :   if (theta_term > 1.0)
     309         687 :     deriv_ap_term = -deriv_ap_term;
     310       46443 :   for (unsigned i = 1; i < num_basis; ++i)
     311       40495 :     if (!(basis_species_gas[i] || kin_stoichiometry(kin, i) == 0.0 || deriv_ap_term == 0.0))
     312       21420 :       drate_dmol[i] += deriv_ap_term * kin_stoichiometry(kin, i) / basis_molality[i];
     313        5948 : }
     314             : }

Generated by: LCOV version 1.14