LCOV - code coverage report
Current view: top level - src/utils - GeochemicalSystem.C (source / functions) Hit Total Coverage
Test: idaholab/moose geochemistry: 2bf808 Lines: 1200 1210 99.2 %
Date: 2025-07-17 01:29:12 Functions: 94 94 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 "GeochemicalSystem.h"
      11             : #include "GeochemistryActivityCalculators.h"
      12             : #include "GeochemistryKineticRateCalculator.h"
      13             : #include "EquilibriumConstantInterpolator.h"
      14             : 
      15        1792 : GeochemicalSystem::GeochemicalSystem(ModelGeochemicalDatabase & mgd,
      16             :                                      GeochemistryActivityCoefficients & gac,
      17             :                                      GeochemistryIonicStrength & is,
      18             :                                      GeochemistrySpeciesSwapper & swapper,
      19             :                                      const std::vector<std::string> & swap_out_of_basis,
      20             :                                      const std::vector<std::string> & swap_into_basis,
      21             :                                      const std::string & charge_balance_species,
      22             :                                      const std::vector<std::string> & constrained_species,
      23             :                                      const std::vector<Real> & constraint_value,
      24             :                                      const MultiMooseEnum & constraint_unit,
      25             :                                      const MultiMooseEnum & constraint_user_meaning,
      26             :                                      Real initial_temperature,
      27             :                                      unsigned iters_to_make_consistent,
      28             :                                      Real min_initial_molality,
      29             :                                      const std::vector<std::string> & kin_name,
      30             :                                      const std::vector<Real> & kin_initial,
      31        1792 :                                      const MultiMooseEnum & kin_unit)
      32        1792 :   : _mgd(mgd),
      33        1792 :     _num_basis(mgd.basis_species_index.size()),
      34        1792 :     _num_eqm(mgd.eqm_species_index.size()),
      35        1792 :     _num_redox(mgd.redox_stoichiometry.m()),
      36        1792 :     _num_surface_pot(mgd.surface_sorption_name.size()),
      37        1792 :     _num_kin(mgd.kin_species_index.size()),
      38        1792 :     _swapper(swapper),
      39        1792 :     _swap_out(swap_out_of_basis),
      40        1792 :     _swap_in(swap_into_basis),
      41        1792 :     _gac(gac),
      42        1792 :     _is(is),
      43        1792 :     _charge_balance_species(charge_balance_species),
      44        1792 :     _original_charge_balance_species(charge_balance_species),
      45        1792 :     _charge_balance_basis_index(0),
      46        1792 :     _constrained_species(constrained_species),
      47        1792 :     _constraint_value(constraint_value),
      48        1792 :     _original_constraint_value(constraint_value),
      49        1792 :     _constraint_unit(constraint_unit.size()),
      50        1792 :     _constraint_user_meaning(constraint_user_meaning.size()),
      51        1792 :     _constraint_meaning(constraint_user_meaning.size()),
      52        1792 :     _eqm_log10K(_num_eqm),
      53        1792 :     _redox_log10K(_num_redox),
      54        1792 :     _kin_log10K(_num_kin),
      55        1792 :     _num_basis_in_algebraic_system(0),
      56        1792 :     _num_in_algebraic_system(0),
      57        1792 :     _in_algebraic_system(_num_basis),
      58        1792 :     _algebraic_index(_num_basis),
      59        1792 :     _basis_index(_num_basis),
      60        1792 :     _bulk_moles_old(_num_basis),
      61        1792 :     _basis_molality(_num_basis),
      62        1792 :     _basis_activity_known(_num_basis),
      63        1792 :     _basis_activity(_num_basis),
      64        1792 :     _eqm_molality(_num_eqm),
      65        1792 :     _basis_activity_coef(_num_basis),
      66        1792 :     _eqm_activity_coef(_num_eqm),
      67        1792 :     _eqm_activity(_num_eqm),
      68        1792 :     _surface_pot_expr(_num_surface_pot),
      69        1792 :     _sorbing_surface_area(_num_surface_pot),
      70        1792 :     _kin_moles(_num_kin),
      71        1792 :     _kin_moles_old(_num_kin),
      72        1792 :     _iters_to_make_consistent(iters_to_make_consistent),
      73        1792 :     _temperature(initial_temperature),
      74        1792 :     _min_initial_molality(min_initial_molality),
      75        1792 :     _original_redox_lhs()
      76             : {
      77       10205 :   for (unsigned i = 0; i < constraint_user_meaning.size(); ++i)
      78        8413 :     _constraint_user_meaning[i] =
      79        8413 :         static_cast<ConstraintUserMeaningEnum>(constraint_user_meaning.get(i));
      80       10205 :   for (unsigned i = 0; i < constraint_unit.size(); ++i)
      81        8413 :     _constraint_unit[i] =
      82        8413 :         static_cast<GeochemistryUnitConverter::GeochemistryUnit>(constraint_unit.get(i));
      83        1792 :   const unsigned ku_size = kin_unit.size();
      84        1792 :   std::vector<GeochemistryUnitConverter::GeochemistryUnit> k_unit(ku_size);
      85        1950 :   for (unsigned i = 0; i < ku_size; ++i)
      86         158 :     k_unit[i] = static_cast<GeochemistryUnitConverter::GeochemistryUnit>(kin_unit.get(i));
      87        1792 :   checkAndInitialize(kin_name, kin_initial, k_unit);
      88        1792 : }
      89             : 
      90         434 : GeochemicalSystem::GeochemicalSystem(
      91             :     ModelGeochemicalDatabase & mgd,
      92             :     GeochemistryActivityCoefficients & gac,
      93             :     GeochemistryIonicStrength & is,
      94             :     GeochemistrySpeciesSwapper & swapper,
      95             :     const std::vector<std::string> & swap_out_of_basis,
      96             :     const std::vector<std::string> & swap_into_basis,
      97             :     const std::string & charge_balance_species,
      98             :     const std::vector<std::string> & constrained_species,
      99             :     const std::vector<Real> & constraint_value,
     100             :     const std::vector<GeochemistryUnitConverter::GeochemistryUnit> & constraint_unit,
     101             :     const std::vector<ConstraintUserMeaningEnum> & constraint_user_meaning,
     102             :     Real initial_temperature,
     103             :     unsigned iters_to_make_consistent,
     104             :     Real min_initial_molality,
     105             :     const std::vector<std::string> & kin_name,
     106             :     const std::vector<Real> & kin_initial,
     107         434 :     const std::vector<GeochemistryUnitConverter::GeochemistryUnit> & kin_unit)
     108         434 :   : _mgd(mgd),
     109         434 :     _num_basis(mgd.basis_species_index.size()),
     110         434 :     _num_eqm(mgd.eqm_species_index.size()),
     111         434 :     _num_redox(mgd.redox_stoichiometry.m()),
     112         434 :     _num_surface_pot(mgd.surface_sorption_name.size()),
     113         434 :     _num_kin(mgd.kin_species_index.size()),
     114         434 :     _swapper(swapper),
     115         434 :     _swap_out(swap_out_of_basis),
     116         434 :     _swap_in(swap_into_basis),
     117         434 :     _gac(gac),
     118         434 :     _is(is),
     119         434 :     _charge_balance_species(charge_balance_species),
     120         434 :     _original_charge_balance_species(charge_balance_species),
     121         434 :     _charge_balance_basis_index(0),
     122         434 :     _constrained_species(constrained_species),
     123         434 :     _constraint_value(constraint_value),
     124         434 :     _original_constraint_value(constraint_value),
     125         434 :     _constraint_unit(constraint_unit),
     126         434 :     _constraint_user_meaning(constraint_user_meaning),
     127         472 :     _constraint_meaning(constraint_user_meaning.size()),
     128         472 :     _eqm_log10K(_num_eqm),
     129         472 :     _redox_log10K(_num_redox),
     130         472 :     _kin_log10K(_num_kin),
     131         434 :     _num_basis_in_algebraic_system(0),
     132         434 :     _num_in_algebraic_system(0),
     133         472 :     _in_algebraic_system(_num_basis),
     134         472 :     _algebraic_index(_num_basis),
     135         472 :     _basis_index(_num_basis),
     136         472 :     _bulk_moles_old(_num_basis),
     137         472 :     _basis_molality(_num_basis),
     138         472 :     _basis_activity_known(_num_basis),
     139         472 :     _basis_activity(_num_basis),
     140         472 :     _eqm_molality(_num_eqm),
     141         472 :     _basis_activity_coef(_num_basis),
     142         472 :     _eqm_activity_coef(_num_eqm),
     143         472 :     _eqm_activity(_num_eqm),
     144         472 :     _surface_pot_expr(_num_surface_pot),
     145         472 :     _sorbing_surface_area(_num_surface_pot),
     146         472 :     _kin_moles(_num_kin),
     147         472 :     _kin_moles_old(_num_kin),
     148         434 :     _iters_to_make_consistent(iters_to_make_consistent),
     149         434 :     _temperature(initial_temperature),
     150         434 :     _min_initial_molality(min_initial_molality),
     151         434 :     _original_redox_lhs()
     152             : {
     153         434 :   checkAndInitialize(kin_name, kin_initial, kin_unit);
     154         510 : }
     155             : 
     156             : void
     157        2226 : GeochemicalSystem::checkAndInitialize(
     158             :     const std::vector<std::string> & kin_name,
     159             :     const std::vector<Real> & kin_initial,
     160             :     const std::vector<GeochemistryUnitConverter::GeochemistryUnit> & kin_unit)
     161             : {
     162             :   // initialize every the kinetic species
     163        2226 :   const unsigned num_kin_name = kin_name.size();
     164        2226 :   if (!(_num_kin == num_kin_name && _num_kin == kin_initial.size() && _num_kin == kin_unit.size()))
     165           5 :     mooseError("Initial mole number (or mass or volume) and a unit must be provided for each "
     166             :                "kinetic species ",
     167           5 :                _num_kin,
     168             :                " ",
     169             :                num_kin_name,
     170             :                " ",
     171           5 :                kin_initial.size(),
     172             :                " ",
     173           5 :                kin_unit.size());
     174        2512 :   for (const auto & name_index : _mgd.kin_species_index)
     175             :   {
     176         292 :     const unsigned ind = std::distance(
     177         292 :         kin_name.begin(), std::find(kin_name.begin(), kin_name.end(), name_index.first));
     178         292 :     if (ind < num_kin_name)
     179             :     {
     180         294 :       if (!(kin_unit[ind] == GeochemistryUnitConverter::GeochemistryUnit::MOLES ||
     181          46 :             kin_unit[ind] == GeochemistryUnitConverter::GeochemistryUnit::KG ||
     182          24 :             kin_unit[ind] == GeochemistryUnitConverter::GeochemistryUnit::G ||
     183           2 :             kin_unit[ind] == GeochemistryUnitConverter::GeochemistryUnit::MG ||
     184             :             kin_unit[ind] == GeochemistryUnitConverter::GeochemistryUnit::UG ||
     185             :             kin_unit[ind] == GeochemistryUnitConverter::GeochemistryUnit::CM3))
     186           1 :         mooseError("Kinetic species ",
     187             :                    name_index.first,
     188             :                    ": units must be moles or mass, or volume in the case of minerals");
     189         291 :       const Real moles = GeochemistryUnitConverter::toMoles(
     190         291 :           kin_initial[ind], kin_unit[ind], name_index.first, _mgd);
     191         291 :       setKineticMoles(name_index.second, moles);
     192             :     }
     193             :     else
     194           0 :       mooseError("Initial mole number or mass or volume for kinetic species ",
     195             :                  name_index.first,
     196             :                  " must be provided");
     197             :   }
     198             : 
     199             :   // check sanity of swaps
     200        2220 :   if (_swap_out.size() != _swap_in.size())
     201           1 :     mooseError("swap_out_of_basis must have same length as swap_into_basis");
     202        2872 :   for (unsigned i = 0; i < _swap_out.size(); ++i)
     203         654 :     if (_swap_out[i] == _charge_balance_species)
     204           1 :       mooseError("Cannot swap out ",
     205             :                  _charge_balance_species,
     206             :                  " because it is the charge-balance species\n");
     207             : 
     208             :   // do swaps desired by user.  any exception here is an error
     209        2870 :   for (unsigned i = 0; i < _swap_out.size(); ++i)
     210             :     try
     211             :     {
     212         653 :       _swapper.performSwap(_mgd, _swap_out[i], _swap_in[i]);
     213             :     }
     214           0 :     catch (const MooseException & e)
     215             :     {
     216           0 :       mooseError(e.what());
     217           0 :     }
     218             : 
     219             :   // check charge-balance species is in the basis and has a charge
     220        2217 :   if (_mgd.basis_species_index.count(_charge_balance_species) == 0)
     221           1 :     mooseError("Cannot enforce charge balance using ",
     222             :                _charge_balance_species,
     223             :                " because it is not in the basis");
     224        2216 :   _charge_balance_basis_index = _mgd.basis_species_index.at(_charge_balance_species);
     225        2216 :   if (_mgd.basis_species_charge[_charge_balance_basis_index] == 0.0)
     226           1 :     mooseError("Cannot enforce charge balance using ",
     227             :                _charge_balance_species,
     228             :                " because it has zero charge");
     229             : 
     230             :   // check that constraint vectors have appropriate sizes
     231        2215 :   if (_constrained_species.size() != _constraint_value.size())
     232           1 :     mooseError("Constrained species names must have same length as constraint values");
     233        2214 :   if (_constrained_species.size() != _constraint_unit.size())
     234           1 :     mooseError("Constrained species names must have same length as constraint units");
     235        2213 :   if (_constrained_species.size() != _constraint_user_meaning.size())
     236           1 :     mooseError("Constrained species names must have same length as constraint meanings");
     237        2212 :   if (_constrained_species.size() != _num_basis)
     238           1 :     mooseError("Constrained species names must have same length as the number of species in the "
     239             :                "basis (each component must be provided with a single constraint");
     240             : 
     241             :   // check that each _constrained_species name appears in the basis
     242       12798 :   for (const auto & name : _mgd.basis_species_name)
     243       10589 :     if (std::find(_constrained_species.begin(), _constrained_species.end(), name) ==
     244             :         _constrained_species.end())
     245           2 :       mooseError("The basis species ", name, " must appear in the constrained species list");
     246             : 
     247             :   // order the constraints in the same way as the basis species.  This makes the remainder of the
     248             :   // code much cleaner.
     249        2209 :   std::vector<std::string> c_s(_num_basis);
     250        2209 :   std::vector<Real> c_v(_num_basis);
     251        2230 :   std::vector<GeochemistryUnitConverter::GeochemistryUnit> c_u(_num_basis);
     252        2230 :   std::vector<ConstraintUserMeaningEnum> c_m(_num_basis);
     253       12792 :   for (unsigned i = 0; i < _num_basis; ++i)
     254             :   {
     255       10583 :     const unsigned basis_ind = _mgd.basis_species_index.at(_constrained_species[i]);
     256       10583 :     c_s[basis_ind] = _constrained_species[i];
     257       10583 :     c_v[basis_ind] = _constraint_value[i];
     258       10583 :     c_u[basis_ind] = _constraint_unit[i];
     259       10583 :     c_m[basis_ind] = _constraint_user_meaning[i];
     260             :   }
     261        2209 :   _constrained_species = c_s;
     262        2209 :   _constraint_value = c_v;
     263        2209 :   _constraint_unit = c_u;
     264        2209 :   _original_constraint_value = c_v;
     265        2209 :   _constraint_user_meaning = c_m;
     266             : 
     267             :   // run through the constraints, checking physical and chemical consistency, converting to mole
     268             :   // units, and building constraint_meaning
     269       12755 :   for (unsigned i = 0; i < _constrained_species.size(); ++i)
     270             :   {
     271       10567 :     const std::string name = _constrained_species[i];
     272             : 
     273       10567 :     switch (_constraint_user_meaning[i])
     274             :     {
     275         749 :       case ConstraintUserMeaningEnum::KG_SOLVENT_WATER:
     276             :       {
     277             :         // if the mass of solvent water is provided, check it is positive
     278         749 :         if (_constraint_value[i] <= 0.0)
     279           1 :           mooseError("Specified mass of solvent water must be positive: you entered ",
     280             :                      _constraint_value[i]);
     281         748 :         if (_constraint_unit[i] != GeochemistryUnitConverter::GeochemistryUnit::KG)
     282           1 :           mooseError("Units for kg_solvent_water must be kg");
     283         747 :         _constraint_meaning[i] = ConstraintMeaningEnum::KG_SOLVENT_WATER;
     284         747 :         break;
     285             :       }
     286        6789 :       case ConstraintUserMeaningEnum::BULK_COMPOSITION:
     287             :       case ConstraintUserMeaningEnum::BULK_COMPOSITION_WITH_KINETIC:
     288             :       {
     289             :         // convert to mole units and specify correct constraint_meaning
     290        6789 :         _constraint_value[i] = GeochemistryUnitConverter::toMoles(
     291        6789 :             _constraint_value[i], _constraint_unit[i], name, _mgd);
     292             :         // add contributions from kinetic moles, if necessary
     293        6789 :         if (_constraint_user_meaning[i] == ConstraintUserMeaningEnum::BULK_COMPOSITION)
     294        7625 :           for (unsigned k = 0; k < _mgd.kin_species_name.size(); ++k)
     295         849 :             _constraint_value[i] += _kin_moles[k] * _mgd.kin_stoichiometry(k, i);
     296        6802 :         if (!(_constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::MOLES ||
     297         234 :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::KG ||
     298         233 :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::G ||
     299             :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::MG ||
     300             :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::UG))
     301           1 :           mooseError("Species ", name, ": units for bulk composition must be moles or mass");
     302        6788 :         if (name == "H2O")
     303        1112 :           _constraint_meaning[i] = ConstraintMeaningEnum::MOLES_BULK_WATER;
     304             :         else
     305        5676 :           _constraint_meaning[i] = ConstraintMeaningEnum::MOLES_BULK_SPECIES;
     306             :         break;
     307             :       }
     308         711 :       case ConstraintUserMeaningEnum::FREE_CONCENTRATION:
     309             :       {
     310             :         // if free concentration, check it is positive and perform the translation to mole units
     311         711 :         if (_constraint_value[i] <= 0.0)
     312           1 :           mooseError("Specified free concentration values must be positive: you entered ",
     313             :                      _constraint_value[i]);
     314         711 :         if (!(_constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::MOLAL ||
     315             :               _constraint_unit[i] ==
     316           2 :                   GeochemistryUnitConverter::GeochemistryUnit::KG_PER_KG_SOLVENT ||
     317             :               _constraint_unit[i] ==
     318           1 :                   GeochemistryUnitConverter::GeochemistryUnit::G_PER_KG_SOLVENT ||
     319             :               _constraint_unit[i] ==
     320             :                   GeochemistryUnitConverter::GeochemistryUnit::MG_PER_KG_SOLVENT ||
     321             :               _constraint_unit[i] ==
     322             :                   GeochemistryUnitConverter::GeochemistryUnit::UG_PER_KG_SOLVENT))
     323           1 :           mooseError(
     324             :               "Species ",
     325             :               name,
     326             :               ": units for free concentration quantities must be molal or mass_per_kg_solvent");
     327        1418 :         _constraint_value[i] = GeochemistryUnitConverter::toMoles(
     328         709 :             _constraint_value[i], _constraint_unit[i], name, _mgd);
     329         709 :         _constraint_meaning[i] = ConstraintMeaningEnum::FREE_MOLALITY;
     330         709 :         break;
     331             :       }
     332         362 :       case ConstraintUserMeaningEnum::FREE_MINERAL:
     333             :       {
     334             :         // if free mineral, check it is positive and perform the translation to mole units
     335         362 :         if (_constraint_value[i] <= 0.0)
     336           1 :           mooseError("Specified free mineral values must be positive: you entered ",
     337             :                      _constraint_value[i]);
     338         429 :         if (!(_constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::MOLES ||
     339          90 :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::KG ||
     340          90 :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::G ||
     341          68 :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::MG ||
     342             :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::UG ||
     343             :               _constraint_unit[i] == GeochemistryUnitConverter::GeochemistryUnit::CM3))
     344           1 :           mooseError("Species ",
     345             :                      name,
     346             :                      ": units for free mineral quantities must be moles, mass or volume");
     347         720 :         _constraint_value[i] = GeochemistryUnitConverter::toMoles(
     348         360 :             _constraint_value[i], _constraint_unit[i], name, _mgd);
     349         360 :         _constraint_meaning[i] = ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES;
     350         360 :         break;
     351             :       }
     352        1288 :       case ConstraintUserMeaningEnum::ACTIVITY:
     353             :       {
     354        1288 :         if (_constraint_value[i] <= 0.0)
     355           1 :           mooseError("Specified activity values must be positive: you entered ",
     356             :                      _constraint_value[i]);
     357        1287 :         if (_constraint_unit[i] != GeochemistryUnitConverter::GeochemistryUnit::DIMENSIONLESS)
     358           1 :           mooseError(
     359             :               "Species ", name, ": dimensionless units must be used when specifying activity");
     360        1286 :         _constraint_meaning[i] = ConstraintMeaningEnum::ACTIVITY;
     361        1286 :         break;
     362             :       }
     363         561 :       case ConstraintUserMeaningEnum::LOG10ACTIVITY:
     364             :       {
     365             :         // if log10activity is provided, convert it to activity
     366         561 :         if (_constraint_unit[i] != GeochemistryUnitConverter::GeochemistryUnit::DIMENSIONLESS)
     367           1 :           mooseError("Species ",
     368             :                      name,
     369             :                      ": dimensionless units must be used when specifying log10activity\n");
     370         560 :         _constraint_value[i] = std::pow(10.0, _constraint_value[i]);
     371         560 :         _constraint_meaning[i] = ConstraintMeaningEnum::ACTIVITY;
     372         560 :         break;
     373             :       }
     374          72 :       case ConstraintUserMeaningEnum::FUGACITY:
     375             :       {
     376             :         // if fugacity is provided, check it is positive
     377          72 :         if (_constraint_value[i] <= 0.0)
     378           1 :           mooseError("Specified fugacity values must be positive: you entered ",
     379             :                      _constraint_value[i]);
     380          71 :         if (_constraint_unit[i] != GeochemistryUnitConverter::GeochemistryUnit::DIMENSIONLESS)
     381           1 :           mooseError(
     382             :               "Species ", name, ": dimensionless units must be used when specifying fugacity\n");
     383          70 :         _constraint_meaning[i] = ConstraintMeaningEnum::FUGACITY;
     384          70 :         break;
     385             :       }
     386          35 :       case ConstraintUserMeaningEnum::LOG10FUGACITY:
     387             :       {
     388             :         // if log10fugacity is provided, convert it to fugacity
     389          35 :         if (_constraint_unit[i] != GeochemistryUnitConverter::GeochemistryUnit::DIMENSIONLESS)
     390           1 :           mooseError("Species ",
     391             :                      name,
     392             :                      ": dimensionless units must be used when specifying log10fugacity\n");
     393          34 :         _constraint_value[i] = std::pow(10.0, _constraint_value[i]);
     394          34 :         _constraint_meaning[i] = ConstraintMeaningEnum::FUGACITY;
     395          34 :         break;
     396             :       }
     397             :     }
     398             : 
     399             :     // check that water is provided with correct meaning
     400       10554 :     if (name == "H2O")
     401        2206 :       if (!(_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_WATER ||
     402             :             _constraint_meaning[i] == ConstraintMeaningEnum::KG_SOLVENT_WATER ||
     403             :             _constraint_meaning[i] == ConstraintMeaningEnum::ACTIVITY))
     404           1 :         mooseError("H2O must be provided with either a mass of solvent water, a bulk composition "
     405             :                    "in moles or mass, or an activity");
     406             : 
     407             :     // check that gases are provided with the correct meaning
     408       10553 :     if (_mgd.basis_species_gas[i])
     409         104 :       if (_constraint_meaning[i] != ConstraintMeaningEnum::FUGACITY)
     410           1 :         mooseError("The gas ", name, " must be provided with a fugacity");
     411             : 
     412             :     // check that minerals are provided with the correct meaning
     413       10552 :     if (_mgd.basis_species_mineral[i])
     414         503 :       if (!(_constraint_meaning[i] == ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES ||
     415             :             _constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES))
     416           2 :         mooseError("The mineral ",
     417             :                    name,
     418             :                    " must be provided with either: a free number of moles, a free mass or a free "
     419             :                    "volume; or a bulk composition of moles or mass");
     420             : 
     421             :     // check that non-water, non-minerals, non-gases are provided with the correct meaning
     422       10550 :     if (name != "H2O" && !_mgd.basis_species_gas[i] && !_mgd.basis_species_mineral[i])
     423        7741 :       if (!(_constraint_meaning[i] == ConstraintMeaningEnum::FREE_MOLALITY ||
     424             :             _constraint_meaning[i] == ConstraintMeaningEnum::ACTIVITY ||
     425             :             _constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES))
     426           2 :         mooseError("The basis species ",
     427             :                    name,
     428             :                    " must be provided with a free concentration, bulk composition or an activity");
     429             : 
     430             :     // check that the charge-balance species has been provided MOLES_BULK_SPECIES
     431       10548 :     if (name == _charge_balance_species)
     432        2203 :       if (_constraint_meaning[i] != ConstraintMeaningEnum::MOLES_BULK_SPECIES)
     433           2 :         mooseError("For code consistency, the species ",
     434             :                    name,
     435             :                    " must be provided with a bulk composition because it is the charge-balance "
     436             :                    "species.  The value provided should be a reasonable estimate of the mole "
     437             :                    "number, but will be overridden as the solve progresses");
     438             :   }
     439        2188 :   _original_redox_lhs = _mgd.redox_lhs;
     440             : 
     441        2188 :   initialize();
     442        2209 : }
     443             : 
     444             : void
     445        2188 : GeochemicalSystem::initialize()
     446             : {
     447        2188 :   buildTemperatureDependentQuantities(_temperature);
     448        2188 :   enforceChargeBalanceIfSimple(_constraint_value, _bulk_moles_old);
     449        2188 :   buildAlgebraicInfo(_in_algebraic_system,
     450        2188 :                      _num_basis_in_algebraic_system,
     451        2188 :                      _num_in_algebraic_system,
     452        2188 :                      _algebraic_index,
     453        2188 :                      _basis_index);
     454        2188 :   initBulkAndFree(_bulk_moles_old, _basis_molality);
     455        2188 :   buildKnownBasisActivities(_basis_activity_known, _basis_activity);
     456             : 
     457        2188 :   _eqm_molality.assign(_num_eqm, 0.0);
     458        2188 :   _surface_pot_expr.assign(_num_surface_pot, 1.0);
     459             : 
     460        2188 :   computeConsistentConfiguration();
     461        2188 : }
     462             : 
     463             : void
     464       47936 : GeochemicalSystem::computeConsistentConfiguration()
     465             : {
     466             :   // the steps 1 and 2 below could be iterated for a long time (or a Newton process could even be
     467             :   // followed) to provide better estimates of activities and molalities, but this is not done in the
     468             :   // conventional geochemistry approach: there are just too many unknowns and approximations
     469             :   // employed during the algebraic-system solve to justify iterating towards the perfectly
     470             :   // consistent initial condition
     471       95908 :   for (unsigned picard = 0; picard < _iters_to_make_consistent + 1; ++picard)
     472             :   {
     473             :     // Step 1: compute ionic strengths and activities using the eqm molalities
     474       47972 :     _gac.setInternalParameters(_temperature, _mgd, _basis_molality, _eqm_molality, _kin_moles);
     475       47972 :     _gac.buildActivityCoefficients(_mgd, _basis_activity_coef, _eqm_activity_coef);
     476       47972 :     updateBasisMolalityForKnownActivity(_basis_molality);
     477       47972 :     computeRemainingBasisActivities(_basis_activity);
     478             : 
     479             :     // Step 2: compute equilibrium molality based on the activities just computed
     480       47972 :     computeEqmMolalities(_eqm_molality);
     481             :   }
     482             : 
     483       47936 :   computeBulk(_bulk_moles_old);
     484       47936 :   computeFreeMineralMoles(_basis_molality);
     485       47936 :   computeSorbingSurfaceArea(_sorbing_surface_area);
     486       47936 : }
     487             : 
     488             : unsigned
     489        2814 : GeochemicalSystem::getChargeBalanceBasisIndex() const
     490             : {
     491        2814 :   return _charge_balance_basis_index;
     492             : }
     493             : 
     494             : Real
     495        2516 : GeochemicalSystem::getLog10K(unsigned j) const
     496             : {
     497        2516 :   if (j >= _num_eqm)
     498           1 :     mooseError("Cannot retrieve log10K for equilibrium species ",
     499             :                j,
     500             :                " since there are only ",
     501           1 :                _num_eqm,
     502             :                " equilibrium species");
     503        2515 :   return _eqm_log10K[j];
     504             : }
     505             : 
     506             : unsigned
     507           8 : GeochemicalSystem::getNumRedox() const
     508             : {
     509           8 :   return _num_redox;
     510             : }
     511             : 
     512             : Real
     513         143 : GeochemicalSystem::getRedoxLog10K(unsigned red) const
     514             : {
     515         143 :   if (red >= _num_redox)
     516           1 :     mooseError("Cannot retrieve log10K for redox species ",
     517             :                red,
     518             :                " since there are only ",
     519           1 :                _num_redox,
     520             :                " redox species");
     521         142 :   return _redox_log10K[red];
     522             : }
     523             : 
     524             : Real
     525         143 : GeochemicalSystem::log10RedoxActivityProduct(unsigned red) const
     526             : {
     527         143 :   if (red >= _num_redox)
     528           1 :     mooseError("Cannot retrieve activity product for redox species ",
     529             :                red,
     530             :                " since there are only ",
     531           1 :                _num_redox,
     532             :                " redox species");
     533             :   Real log10ap = 0.0;
     534        2268 :   for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
     535        2126 :     log10ap += _mgd.redox_stoichiometry(red, basis_i) * std::log10(_basis_activity[basis_i]);
     536         142 :   return log10ap;
     537             : }
     538             : 
     539             : unsigned
     540         681 : GeochemicalSystem::getNumKinetic() const
     541             : {
     542         681 :   return _num_kin;
     543             : }
     544             : 
     545             : Real
     546          55 : GeochemicalSystem::getKineticLog10K(unsigned kin) const
     547             : {
     548          55 :   if (kin >= _num_kin)
     549           1 :     mooseError("Cannot retrieve log10K for kinetic species ",
     550             :                kin,
     551             :                " since there are only ",
     552           1 :                _num_kin,
     553             :                " kinetic species");
     554          54 :   return _kin_log10K[kin];
     555             : }
     556             : 
     557             : const std::vector<Real> &
     558           2 : GeochemicalSystem::getKineticLog10K() const
     559             : {
     560           2 :   return _kin_log10K;
     561             : }
     562             : 
     563             : Real
     564        5963 : GeochemicalSystem::log10KineticActivityProduct(unsigned kin) const
     565             : {
     566        5963 :   if (kin >= _num_kin)
     567           1 :     mooseError("Cannot retrieve activity product for kinetic species ",
     568             :                kin,
     569             :                " since there are only ",
     570           1 :                _num_kin,
     571             :                " kinetic species");
     572             :   Real log10ap = 0.0;
     573       52501 :   for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
     574       46539 :     log10ap += _mgd.kin_stoichiometry(kin, basis_i) * std::log10(_basis_activity[basis_i]);
     575        5962 :   return log10ap;
     576             : }
     577             : 
     578             : void
     579        3064 : GeochemicalSystem::buildTemperatureDependentQuantities(Real temperature)
     580             : {
     581        3064 :   const std::vector<Real> temps = _mgd.original_database->getTemperatures();
     582        3064 :   const unsigned numT = temps.size();
     583        3064 :   const std::string model_type = _mgd.original_database->getLogKModel();
     584             : 
     585       65874 :   for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
     586             :   {
     587             :     EquilibriumConstantInterpolator interp(
     588       62810 :         temps, _mgd.eqm_log10K.sub_matrix(eqm_j, 1, 0, numT).get_values(), model_type);
     589       62810 :     interp.generate();
     590       62810 :     _eqm_log10K[eqm_j] = interp.sample(temperature);
     591       62810 :   }
     592        4021 :   for (unsigned red = 0; red < _num_redox; ++red)
     593             :   {
     594             :     EquilibriumConstantInterpolator interp(
     595         957 :         temps, _mgd.redox_log10K.sub_matrix(red, 1, 0, numT).get_values(), model_type);
     596         957 :     interp.generate();
     597         957 :     _redox_log10K[red] = interp.sample(temperature);
     598         957 :   }
     599        3671 :   for (unsigned kin = 0; kin < _num_kin; ++kin)
     600             :   {
     601             :     EquilibriumConstantInterpolator interp(
     602         607 :         temps, _mgd.kin_log10K.sub_matrix(kin, 1, 0, numT).get_values(), model_type);
     603         607 :     interp.generate();
     604         607 :     _kin_log10K[kin] = interp.sample(temperature);
     605         607 :   }
     606        3064 : }
     607             : 
     608             : void
     609        3000 : GeochemicalSystem::buildAlgebraicInfo(std::vector<bool> & in_algebraic_system,
     610             :                                       unsigned & num_basis_in_algebraic_system,
     611             :                                       unsigned & num_in_algebraic_system,
     612             :                                       std::vector<unsigned> & algebraic_index,
     613             :                                       std::vector<unsigned> & basis_index) const
     614             : {
     615             :   // build in_algebraic_system
     616       21142 :   for (const auto & name_index : _mgd.basis_species_index)
     617             :   {
     618       18142 :     const std::string name = name_index.first;
     619       18142 :     const unsigned basis_ind = name_index.second;
     620       18142 :     const ConstraintMeaningEnum meaning = _constraint_meaning[basis_ind];
     621       18142 :     if (name == "H2O")
     622        3000 :       in_algebraic_system[basis_ind] = (meaning == ConstraintMeaningEnum::MOLES_BULK_WATER);
     623       15142 :     else if (_mgd.basis_species_gas[basis_ind])
     624             :       in_algebraic_system[basis_ind] = false;
     625       14930 :     else if (_mgd.basis_species_mineral[basis_ind])
     626             :       in_algebraic_system[basis_ind] = false;
     627             :     else
     628       13071 :       in_algebraic_system[basis_ind] = (meaning == ConstraintMeaningEnum::MOLES_BULK_SPECIES);
     629             :   }
     630             : 
     631             :   // build algebraic_index and basis_index
     632        3000 :   num_basis_in_algebraic_system = 0;
     633        3000 :   algebraic_index.resize(_num_basis, 0);
     634        3000 :   basis_index.resize(_num_basis, 0);
     635       21142 :   for (unsigned basis_ind = 0; basis_ind < _num_basis; ++basis_ind)
     636       18142 :     if (in_algebraic_system[basis_ind])
     637             :     {
     638       11987 :       algebraic_index[basis_ind] = _num_basis_in_algebraic_system;
     639       11987 :       basis_index[_num_basis_in_algebraic_system] = basis_ind;
     640       11987 :       num_basis_in_algebraic_system += 1;
     641             :     }
     642             : 
     643        3000 :   num_in_algebraic_system = num_basis_in_algebraic_system + _num_surface_pot + _num_kin;
     644        3000 : }
     645             : 
     646             : unsigned
     647        4946 : GeochemicalSystem::getNumInAlgebraicSystem() const
     648             : {
     649        4946 :   return _num_in_algebraic_system;
     650             : }
     651             : 
     652             : unsigned
     653        4890 : GeochemicalSystem::getNumBasisInAlgebraicSystem() const
     654             : {
     655        4890 :   return _num_basis_in_algebraic_system;
     656             : }
     657             : 
     658             : unsigned
     659         342 : GeochemicalSystem::getNumSurfacePotentials() const
     660             : {
     661         342 :   return _num_surface_pot;
     662             : }
     663             : 
     664             : const std::vector<bool> &
     665          18 : GeochemicalSystem::getInAlgebraicSystem() const
     666             : {
     667          18 :   return _in_algebraic_system;
     668             : }
     669             : 
     670             : const std::vector<unsigned> &
     671           5 : GeochemicalSystem::getBasisIndexOfAlgebraicSystem() const
     672             : {
     673           5 :   return _basis_index;
     674             : }
     675             : 
     676             : const std::vector<unsigned> &
     677           5 : GeochemicalSystem::getAlgebraicIndexOfBasisSystem() const
     678             : {
     679           5 :   return _algebraic_index;
     680             : }
     681             : 
     682             : std::vector<Real>
     683       42470 : GeochemicalSystem::getAlgebraicVariableValues() const
     684             : {
     685       42470 :   std::vector<Real> var(_num_basis_in_algebraic_system + _num_surface_pot + _num_kin);
     686      244991 :   for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
     687      202521 :     var[a] = _basis_molality[_basis_index[a]];
     688       45629 :   for (unsigned s = 0; s < _num_surface_pot; ++s)
     689        3159 :     var[s + _num_basis_in_algebraic_system] = _surface_pot_expr[s];
     690       47731 :   for (unsigned k = 0; k < _num_kin; ++k)
     691        5261 :     var[k + _num_basis_in_algebraic_system + _num_surface_pot] = _kin_moles[k];
     692       42470 :   return var;
     693             : }
     694             : 
     695             : std::vector<Real>
     696        1881 : GeochemicalSystem::getAlgebraicBasisValues() const
     697             : {
     698        1881 :   std::vector<Real> var(_num_basis_in_algebraic_system);
     699       19530 :   for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
     700       17649 :     var[a] = _basis_molality[_basis_index[a]];
     701        1881 :   return var;
     702             : }
     703             : 
     704             : DenseVector<Real>
     705           6 : GeochemicalSystem::getAlgebraicVariableDenseValues() const
     706             : {
     707           6 :   DenseVector<Real> var(_num_in_algebraic_system);
     708          16 :   for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
     709          10 :     var(a) = _basis_molality[_basis_index[a]];
     710           7 :   for (unsigned s = 0; s < _num_surface_pot; ++s)
     711           1 :     var(s + _num_basis_in_algebraic_system) = _surface_pot_expr[s];
     712           9 :   for (unsigned k = 0; k < _num_kin; ++k)
     713           3 :     var(k + _num_basis_in_algebraic_system + _num_surface_pot) = _kin_moles[k];
     714           6 :   return var;
     715             : }
     716             : 
     717             : void
     718        2188 : GeochemicalSystem::initBulkAndFree(std::vector<Real> & bulk_moles_old,
     719             :                                    std::vector<Real> & basis_molality) const
     720             : {
     721       12696 :   for (unsigned i = 0; i < _num_basis; ++i)
     722             :   {
     723             :     // water is done first, so dividing by basis_molality[0] is OK
     724       10508 :     const Real value = _constraint_value[i];
     725       10508 :     const ConstraintMeaningEnum meaning = _constraint_meaning[i];
     726       10508 :     switch (meaning)
     727             :     {
     728        1111 :       case ConstraintMeaningEnum::MOLES_BULK_WATER:
     729             :       {
     730        1111 :         bulk_moles_old[i] = value;
     731        1111 :         basis_molality[i] = std::max(
     732        1111 :             _min_initial_molality,
     733        1111 :             0.999 * value /
     734             :                 GeochemistryConstants::MOLES_PER_KG_WATER); // mass of solvent water (water is an
     735             :                                                             // algebraic variable). Guess used to
     736             :                                                             // initialize the Newton process
     737        1111 :         break;
     738             :       }
     739         743 :       case ConstraintMeaningEnum::KG_SOLVENT_WATER:
     740             :       {
     741         743 :         bulk_moles_old[i] = value * GeochemistryConstants::MOLES_PER_KG_WATER /
     742             :                             0.999; // initial guess (water is not an algebraic variable).  Will be
     743             :                                    // determined exactly during the solve
     744         743 :         basis_molality[i] = value; // mass of solvent water
     745         743 :         break;
     746             :       }
     747        5660 :       case ConstraintMeaningEnum::MOLES_BULK_SPECIES:
     748             :       {
     749        5660 :         bulk_moles_old[i] = value;
     750        5660 :         basis_molality[i] = std::max(
     751        5660 :             _min_initial_molality,
     752        5660 :             0.9 * value / basis_molality[0]); // initial guess (i is an algebraic variable).  This
     753             :                                               // is what we solve for in the Newton process
     754        5660 :         break;
     755             :       }
     756             :       case ConstraintMeaningEnum::FREE_MOLALITY:
     757             :       {
     758         701 :         bulk_moles_old[i] =
     759         701 :             value * basis_molality[0] / 0.9; // initial guess (i is not an algebraic variable).
     760             :                                              // Will be determined exactly during the solve
     761         701 :         basis_molality[i] = value;
     762         701 :         break;
     763             :       }
     764         359 :       case ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES:
     765             :       {
     766         359 :         bulk_moles_old[i] = value / 0.9; // initial guess (i is not an algebraic variable).  Will
     767             :                                          // be determined exactly during the solve
     768         359 :         basis_molality[i] = value;       // note, this is *moles*, not molality
     769         359 :         break;
     770             :       }
     771         103 :       case ConstraintMeaningEnum::FUGACITY:
     772             :       {
     773         103 :         bulk_moles_old[i] = 0.0; // initial guess (i is not an algebraic variable).  will be
     774             :                                  // determined exactly after the solve
     775         103 :         basis_molality[i] =
     776             :             0.0; // never used in any solve process, but since this is a gas should be zero, and
     777             :                  // setting this explicitly elimiates if(species=gas) checks in various loops
     778         103 :         break;
     779             :       }
     780        1831 :       case ConstraintMeaningEnum::ACTIVITY:
     781             :       {
     782        1831 :         bulk_moles_old[i] = value / 0.9; // initial guess (i is not an algebraic variable).  Will
     783             :                                          // be determined exactly during the solve
     784        1831 :         if (i == 0)
     785         334 :           basis_molality[i] = 1.0; // assumption
     786             :         else
     787        1497 :           basis_molality[i] =
     788             :               value /
     789             :               0.9; // assume activity_coefficient = 0.9.  this will be updated during the solve
     790             :         break;
     791             :       }
     792             :     }
     793             :   }
     794        2188 : }
     795             : 
     796             : Real
     797          19 : GeochemicalSystem::getSolventWaterMass() const
     798             : {
     799          19 :   return _basis_molality[0];
     800             : }
     801             : 
     802             : const std::vector<Real> &
     803       38728 : GeochemicalSystem::getBulkMolesOld() const
     804             : {
     805       38728 :   return _bulk_moles_old;
     806             : }
     807             : 
     808             : DenseVector<Real>
     809         326 : GeochemicalSystem::getBulkOldInOriginalBasis() const
     810             : {
     811         326 :   DenseVector<Real> result(_bulk_moles_old);
     812         326 :   if (_mgd.swap_to_original_basis.n() == 0)
     813             :     return result; // no swaps have been performed
     814         156 :   _mgd.swap_to_original_basis.vector_mult_transpose(result, _bulk_moles_old);
     815          78 :   return result;
     816             : }
     817             : 
     818             : DenseVector<Real>
     819         476 : GeochemicalSystem::getTransportedBulkInOriginalBasis() const
     820             : {
     821             :   std::vector<Real> trans_bulk;
     822         476 :   computeTransportedBulkFromMolalities(trans_bulk);
     823             :   DenseVector<Real> result(trans_bulk);
     824         476 :   if (_mgd.swap_to_original_basis.n() == 0)
     825             :     return result; // no swaps have been performed
     826         364 :   _mgd.swap_to_original_basis.vector_mult_transpose(result, trans_bulk);
     827         182 :   return result;
     828             : }
     829             : 
     830             : const std::vector<Real> &
     831       75773 : GeochemicalSystem::getSolventMassAndFreeMolalityAndMineralMoles() const
     832             : {
     833       75773 :   return _basis_molality;
     834             : }
     835             : 
     836             : void
     837        3010 : GeochemicalSystem::buildKnownBasisActivities(std::vector<bool> & basis_activity_known,
     838             :                                              std::vector<Real> & basis_activity) const
     839             : {
     840        3010 :   basis_activity_known = std::vector<bool>(_num_basis, false);
     841        3010 :   basis_activity.resize(_num_basis);
     842             : 
     843             :   // all aqueous species with provided activity, and all gases with fugacity have known activity
     844       21248 :   for (unsigned i = 0; i < _num_basis; ++i)
     845             :   {
     846       18238 :     const ConstraintMeaningEnum meaning = _constraint_meaning[i];
     847       18238 :     if (meaning == ConstraintMeaningEnum::ACTIVITY || meaning == ConstraintMeaningEnum::FUGACITY)
     848             :     {
     849             :       basis_activity_known[i] = true;
     850        2307 :       basis_activity[i] = _constraint_value[i];
     851             :     }
     852             :   }
     853             : 
     854             :   // all minerals have activity = 1.0
     855       21248 :   for (unsigned basis_ind = 0; basis_ind < _num_basis; ++basis_ind)
     856       18238 :     if (_mgd.basis_species_mineral[basis_ind])
     857             :     {
     858             :       basis_activity_known[basis_ind] = true;
     859        1867 :       basis_activity[basis_ind] = 1.0;
     860             :     }
     861        3010 : }
     862             : 
     863             : const std::vector<bool> &
     864        2364 : GeochemicalSystem::getBasisActivityKnown() const
     865             : {
     866        2364 :   return _basis_activity_known;
     867             : }
     868             : 
     869             : Real
     870       44863 : GeochemicalSystem::getBasisActivity(unsigned i) const
     871             : {
     872       44863 :   if (i >= _num_basis)
     873           1 :     mooseError("Cannot retrieve basis activity for species ",
     874             :                i,
     875             :                " since there are only ",
     876           1 :                _num_basis,
     877             :                " basis species");
     878       44862 :   return _basis_activity[i];
     879             : }
     880             : 
     881             : const std::vector<Real> &
     882         318 : GeochemicalSystem::getBasisActivity() const
     883             : {
     884         318 :   return _basis_activity;
     885             : }
     886             : 
     887             : Real
     888      247986 : GeochemicalSystem::getEquilibriumMolality(unsigned j) const
     889             : {
     890      247986 :   if (j >= _num_eqm)
     891           1 :     mooseError("Cannot retrieve molality for equilibrium species ",
     892             :                j,
     893             :                " since there are only ",
     894           1 :                _num_eqm,
     895             :                " equilibrium species");
     896      247985 :   return _eqm_molality[j];
     897             : }
     898             : 
     899             : const std::vector<Real> &
     900         441 : GeochemicalSystem::getEquilibriumMolality() const
     901             : {
     902         441 :   return _eqm_molality;
     903             : }
     904             : 
     905             : Real
     906        5261 : GeochemicalSystem::getKineticMoles(unsigned k) const
     907             : {
     908        5261 :   if (k >= _num_kin)
     909           1 :     mooseError("Cannot retrieve moles for kinetic species ",
     910             :                k,
     911             :                " since there are only ",
     912           1 :                _num_kin,
     913             :                " kinetic species");
     914        5260 :   return _kin_moles[k];
     915             : }
     916             : 
     917             : void
     918         299 : GeochemicalSystem::setKineticMoles(unsigned k, Real moles)
     919             : {
     920         299 :   if (k >= _num_kin)
     921           1 :     mooseError("Cannot set moles for kinetic species ",
     922             :                k,
     923             :                " since there are only ",
     924           1 :                _num_kin,
     925             :                " kinetic species");
     926         298 :   if (moles <= 0.0)
     927           2 :     mooseError("Mole number for kinetic species must be positive, not ", moles);
     928         296 :   _kin_moles[k] = moles;
     929         296 :   _kin_moles_old[k] = moles;
     930         296 : }
     931             : 
     932             : const std::vector<Real> &
     933         480 : GeochemicalSystem::getKineticMoles() const
     934             : {
     935         480 :   return _kin_moles;
     936             : }
     937             : 
     938             : void
     939       47982 : GeochemicalSystem::computeRemainingBasisActivities(std::vector<Real> & basis_activity) const
     940             : {
     941       47982 :   if (!_basis_activity_known[0])
     942       47626 :     basis_activity[0] = _gac.waterActivity();
     943      323200 :   for (unsigned basis_ind = 1; basis_ind < _num_basis; ++basis_ind) // don't loop over water
     944      275218 :     if (!_basis_activity_known[basis_ind]) // basis_activity_known = true for minerals, gases and
     945             :                                            // species with activities provided by the user
     946      209979 :       basis_activity[basis_ind] = _basis_activity_coef[basis_ind] * _basis_molality[basis_ind];
     947       47982 : }
     948             : 
     949             : Real
     950         249 : GeochemicalSystem::getEquilibriumActivityCoefficient(unsigned j) const
     951             : {
     952         249 :   if (j >= _num_eqm)
     953           1 :     mooseError("Cannot retrieve activity coefficient for equilibrium species ",
     954             :                j,
     955             :                " since there are only ",
     956           1 :                _num_eqm,
     957             :                " equilibrium species");
     958         248 :   return _eqm_activity_coef[j];
     959             : }
     960             : 
     961             : const std::vector<Real> &
     962         321 : GeochemicalSystem::getEquilibriumActivityCoefficient() const
     963             : {
     964         321 :   return _eqm_activity_coef;
     965             : }
     966             : 
     967             : Real
     968          44 : GeochemicalSystem::getBasisActivityCoefficient(unsigned i) const
     969             : {
     970          44 :   if (i >= _num_basis)
     971           1 :     mooseError("Cannot retrieve basis activity coefficient for species ",
     972             :                i,
     973             :                " since there are only ",
     974           1 :                _num_basis,
     975             :                " basis species");
     976          43 :   return _basis_activity_coef[i];
     977             : }
     978             : 
     979             : const std::vector<Real> &
     980         316 : GeochemicalSystem::getBasisActivityCoefficient() const
     981             : {
     982         316 :   return _basis_activity_coef;
     983             : }
     984             : 
     985             : void
     986       47982 : GeochemicalSystem::updateBasisMolalityForKnownActivity(std::vector<Real> & basis_molality) const
     987             : {
     988      323200 :   for (unsigned i = 1; i < _num_basis; ++i) // don't loop over water
     989             :   {
     990      275218 :     if (_basis_activity_known[i] && !_mgd.basis_species_mineral[i] && !_mgd.basis_species_gas[i])
     991       19522 :       basis_molality[i] =
     992       19522 :           _basis_activity[i] / _basis_activity_coef[i]; // just those for
     993             :                                                         // which activity is provided by the user
     994             :   }
     995       47982 : }
     996             : 
     997             : Real
     998     1373827 : GeochemicalSystem::log10ActivityProduct(unsigned eqm_j) const
     999             : {
    1000             :   Real log10ap = 0.0;
    1001    18962491 :   for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
    1002    17588664 :     log10ap += _mgd.eqm_stoichiometry(eqm_j, basis_i) * std::log10(_basis_activity[basis_i]);
    1003     1373827 :   return log10ap;
    1004             : }
    1005             : 
    1006             : void
    1007       47972 : GeochemicalSystem::computeEqmMolalities(std::vector<Real> & eqm_molality) const
    1008             : {
    1009     1488049 :   for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
    1010             :   {
    1011     1440077 :     if (_mgd.eqm_species_mineral[eqm_j] || _mgd.eqm_species_gas[eqm_j])
    1012       70945 :       eqm_molality[eqm_j] = 0.0;
    1013             :     else
    1014             :     {
    1015             :       // compute log10 version first, in an attempt to eliminate overflow and underflow problems
    1016             :       // such as 10^(1000)
    1017     1369132 :       const Real log10m = log10ActivityProduct(eqm_j) - _eqm_log10K[eqm_j];
    1018     1369132 :       eqm_molality[eqm_j] =
    1019     1369132 :           std::pow(10.0, log10m) / _eqm_activity_coef[eqm_j] * surfaceSorptionModifier(eqm_j);
    1020             :     }
    1021             :   }
    1022       47972 : }
    1023             : 
    1024             : Real
    1025     1369132 : GeochemicalSystem::surfaceSorptionModifier(unsigned eqm_j) const
    1026             : {
    1027     1369132 :   if (eqm_j >= _num_eqm)
    1028             :     return 1.0;
    1029     1369132 :   if (!_mgd.surface_sorption_related[eqm_j])
    1030             :     return 1.0;
    1031       18714 :   return std::pow(_surface_pot_expr[_mgd.surface_sorption_number[eqm_j]],
    1032       18714 :                   2.0 * _mgd.eqm_species_charge[eqm_j]);
    1033             : }
    1034             : 
    1035             : void
    1036       54999 : GeochemicalSystem::enforceChargeBalanceIfSimple(std::vector<Real> & constraint_value,
    1037             :                                                 std::vector<Real> & bulk_moles_old) const
    1038             : {
    1039             :   Real tot_charge = 0.0;
    1040      418126 :   for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
    1041      375298 :     if (_mgd.basis_species_charge[basis_i] != 0.0)
    1042             :     {
    1043      225401 :       if (_constraint_meaning[basis_i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1044      213230 :         tot_charge += _mgd.basis_species_charge[basis_i] * constraint_value[basis_i];
    1045             :       else
    1046             :         return;
    1047             :     }
    1048             :   // kinetic species are counted in the bulk
    1049             :   // all charged basis species must have been provided with a MOLES_BULK_SPECIES value, so we can
    1050             :   // easily enforce charge neutrality
    1051       42828 :   tot_charge -= _mgd.basis_species_charge[_charge_balance_basis_index] *
    1052             :                 constraint_value[_charge_balance_basis_index];
    1053       42828 :   constraint_value[_charge_balance_basis_index] =
    1054       42828 :       -tot_charge / _mgd.basis_species_charge[_charge_balance_basis_index];
    1055       42828 :   bulk_moles_old[_charge_balance_basis_index] = constraint_value[_charge_balance_basis_index];
    1056             : }
    1057             : 
    1058             : Real
    1059        4895 : GeochemicalSystem::getTotalChargeOld() const
    1060             : {
    1061             :   Real tot_charge = 0.0;
    1062       31663 :   for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
    1063       26768 :     tot_charge += _mgd.basis_species_charge[basis_i] * _bulk_moles_old[basis_i];
    1064             :   // kinetic species already counted in bulk_moles
    1065        4895 :   return tot_charge;
    1066             : }
    1067             : 
    1068             : void
    1069        4570 : GeochemicalSystem::enforceChargeBalance()
    1070             : {
    1071        4570 :   enforceChargeBalance(_constraint_value, _bulk_moles_old);
    1072        4570 : }
    1073             : 
    1074             : void
    1075        4570 : GeochemicalSystem::enforceChargeBalance(std::vector<Real> & constraint_value,
    1076             :                                         std::vector<Real> & bulk_moles_old) const
    1077             : {
    1078        4570 :   const Real tot_charge = getTotalChargeOld();
    1079        4570 :   constraint_value[_charge_balance_basis_index] -=
    1080        4570 :       tot_charge / _mgd.basis_species_charge[_charge_balance_basis_index];
    1081        4570 :   bulk_moles_old[_charge_balance_basis_index] = constraint_value[_charge_balance_basis_index];
    1082        4570 : }
    1083             : 
    1084             : void
    1085       44338 : GeochemicalSystem::setAlgebraicVariables(const DenseVector<Real> & algebraic_var)
    1086             : {
    1087       44338 :   if (algebraic_var.size() != _num_in_algebraic_system)
    1088           1 :     mooseError("Incorrect size in setAlgebraicVariables");
    1089      274766 :   for (unsigned a = 0; a < _num_in_algebraic_system; ++a)
    1090      230431 :     if (algebraic_var(a) <= 0.0)
    1091           2 :       mooseError("Cannot set algebraic variables to non-positive values such as ",
    1092             :                  algebraic_var(a));
    1093             : 
    1094      266241 :   for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
    1095      221906 :     _basis_molality[_basis_index[a]] = algebraic_var(a);
    1096       47568 :   for (unsigned s = 0; s < _num_surface_pot; ++s)
    1097        3233 :     _surface_pot_expr[s] = algebraic_var(s + _num_basis_in_algebraic_system);
    1098       49623 :   for (unsigned k = 0; k < _num_kin; ++k)
    1099        5288 :     _kin_moles[k] = algebraic_var(k + _num_basis_in_algebraic_system + _num_surface_pot);
    1100             : 
    1101       44335 :   computeConsistentConfiguration();
    1102       44335 : }
    1103             : 
    1104             : void
    1105       52515 : GeochemicalSystem::computeBulk(std::vector<Real> & bulk_moles_old) const
    1106             : {
    1107      400506 :   for (unsigned i = 0; i < _num_basis; ++i)
    1108             :   {
    1109      347991 :     const Real value = _constraint_value[i];
    1110      347991 :     const ConstraintMeaningEnum meaning = _constraint_meaning[i];
    1111      347991 :     switch (meaning)
    1112             :     {
    1113      296370 :       case ConstraintMeaningEnum::MOLES_BULK_SPECIES:
    1114             :       case ConstraintMeaningEnum::MOLES_BULK_WATER:
    1115             :       {
    1116      296370 :         bulk_moles_old[i] = value;
    1117      296370 :         break;
    1118             :       }
    1119       51621 :       case ConstraintMeaningEnum::KG_SOLVENT_WATER:
    1120             :       case ConstraintMeaningEnum::FREE_MOLALITY:
    1121             :       case ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES:
    1122             :       case ConstraintMeaningEnum::FUGACITY:
    1123             :       case ConstraintMeaningEnum::ACTIVITY:
    1124             :       {
    1125       51621 :         bulk_moles_old[i] = computeBulkFromMolalities(i);
    1126       51621 :         break;
    1127             :       }
    1128             :     }
    1129             :   }
    1130       52515 : }
    1131             : 
    1132             : Real
    1133       72277 : GeochemicalSystem::computeBulkFromMolalities(unsigned basis_ind) const
    1134             : {
    1135       72277 :   const Real nw = _basis_molality[0];
    1136             :   Real bulk = 0.0;
    1137       72277 :   if (basis_ind == 0)
    1138             :     bulk = GeochemistryConstants::MOLES_PER_KG_WATER;
    1139       58615 :   else if (_mgd.basis_species_mineral[basis_ind])
    1140        4596 :     bulk = _basis_molality[basis_ind] / nw; // because of multiplication by nw, below
    1141             :   else
    1142       54019 :     bulk = _basis_molality[basis_ind];
    1143     3182012 :   for (unsigned j = 0; j < _num_eqm; ++j)
    1144     3109735 :     bulk += _mgd.eqm_stoichiometry(j, basis_ind) * _eqm_molality[j];
    1145       72277 :   bulk *= nw;
    1146       79749 :   for (unsigned kin = 0; kin < _num_kin; ++kin)
    1147        7472 :     bulk += _mgd.kin_stoichiometry(kin, basis_ind) * _kin_moles[kin];
    1148       72277 :   return bulk;
    1149             : }
    1150             : 
    1151             : void
    1152         480 : GeochemicalSystem::computeTransportedBulkFromMolalities(std::vector<Real> & transported_bulk) const
    1153             : {
    1154         480 :   transported_bulk.resize(_num_basis);
    1155         480 :   const Real nw = _basis_molality[0];
    1156        3521 :   for (unsigned i = 0; i < _num_basis; ++i)
    1157             :   {
    1158        3041 :     transported_bulk[i] = 0.0;
    1159        3041 :     if (i == 0)
    1160         480 :       transported_bulk[i] = GeochemistryConstants::MOLES_PER_KG_WATER;
    1161        2561 :     else if (_mgd.basis_species_transported[i])
    1162             :     {
    1163        2337 :       if (_mgd.basis_species_mineral[i])
    1164           0 :         transported_bulk[i] = _basis_molality[i] / nw; // because of multiplication by nw, below
    1165             :       else
    1166        2337 :         transported_bulk[i] = _basis_molality[i];
    1167             :     }
    1168             :     else
    1169             :       transported_bulk[i] = 0.0;
    1170      103789 :     for (unsigned j = 0; j < _num_eqm; ++j)
    1171      100748 :       if (_mgd.eqm_species_transported[j])
    1172       95002 :         transported_bulk[i] += _mgd.eqm_stoichiometry(j, i) * _eqm_molality[j];
    1173        3041 :     transported_bulk[i] *= nw;
    1174             :   }
    1175         480 : }
    1176             : 
    1177             : Real
    1178      253617 : GeochemicalSystem::getResidualComponent(unsigned algebraic_ind,
    1179             :                                         const DenseVector<Real> & mole_additions) const
    1180             : {
    1181      253617 :   if (algebraic_ind >= _num_in_algebraic_system)
    1182           2 :     mooseError("Cannot retrieve residual for algebraic index ",
    1183             :                algebraic_ind,
    1184             :                " because there are only ",
    1185           2 :                _num_basis_in_algebraic_system,
    1186             :                " molalities in the algebraic system and ",
    1187           2 :                _num_surface_pot,
    1188             :                " surface potentials and ",
    1189           2 :                _num_kin,
    1190             :                " kinetic species");
    1191      253615 :   if (mole_additions.size() != _num_basis + _num_kin)
    1192           1 :     mooseError("The increment in mole numbers (mole_additions) needs to be of size ",
    1193           1 :                _num_basis,
    1194             :                " + ",
    1195           1 :                _num_kin,
    1196             :                " but it is of size ",
    1197           1 :                mole_additions.size());
    1198             : 
    1199      253614 :   if (algebraic_ind < _num_basis_in_algebraic_system) // residual for basis molality
    1200             :   {
    1201             :     /*
    1202             :      * Without the special things for water or the charge-balance species, we're trying to solve
    1203             :      * bulk_new = bulk_old + mole_addition
    1204             :      * where bulk_old is known (from previous time-step or, for a steady-state problem, the
    1205             :      * constraint)
    1206             :      * and mole_addition is given to this function
    1207             :      * and bulk_new = nw * (m + sum_eqm[stoi * mol_eqm]) + sum_kin[stoi * mole_kin]
    1208             :      * Hence, the residual is
    1209             :      * R = -(bulk_old + mole_addition) + nw*(m + sum_eqm[stoi*mol_eqm]) + sum_kin[stoi*mole_kin]
    1210             :      * This is seemingly different from Bethke Eqns(16.7)-(16.9), because Bethke bulk ("M") do not
    1211             :      contain the sum_kin[stoi * mol_kin] terms.  Converting the above residual to Bethke's
    1212             :      convention:
    1213             :      * R = -((nw*(m + sum_eqm[stoi*mol_eqm]) + sum_kin[stoi*mole_kin])_old + mole_addition) + nw*(m
    1214             :      + sum_eqm[stoi*mol_eqm]) + sum_kin[stoi*mole_kin]
    1215             :      *   = -(M_old + sum_kin[stoi*mole_kin_old] + mole_addition) + M + sum_kin[stoi*mole_kin]
    1216             :      *   = -(M_old + mole_addition) + M + sum_kin[stoi*kin_mole_addition]
    1217             :      * which is exactly Eqns(16.7)-(16.9).
    1218             :      * One problem with Bethke's approach is that if kin_mole_addition depends on the mole number
    1219             :      of the kinetic species, there is a "hidden" variable (moles of kinetic species) which is kept
    1220             :      constant during the solve.  Here, including the moles of kinetic species as additional
    1221             :      variables allows greater accuracy.
    1222             :      */
    1223      243801 :     const unsigned basis_i = _basis_index[algebraic_ind];
    1224             :     Real res = 0.0;
    1225      243801 :     if (basis_i == 0)
    1226       36884 :       res += -(_bulk_moles_old[basis_i] + mole_additions(basis_i)) +
    1227       36884 :              _basis_molality[0] * GeochemistryConstants::MOLES_PER_KG_WATER;
    1228      206917 :     else if (basis_i == _charge_balance_basis_index)
    1229             :     {
    1230       49229 :       res += _basis_molality[0] * _basis_molality[basis_i];
    1231      380244 :       for (unsigned i = 0; i < _num_basis; ++i)
    1232             :       {
    1233      331015 :         if (i == _charge_balance_basis_index)
    1234       49229 :           continue;
    1235      281786 :         else if (_mgd.basis_species_charge[i] ==
    1236             :                  0.0) // certainly includes water, minerals and gases
    1237      119189 :           continue;
    1238      162597 :         else if (_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1239             :         {
    1240             :           // the molalities might not yet have converged so that the bulk moles of this species
    1241             :           // currently equals _bulk_moles_old + mole_additions, but we know
    1242             :           // that when the solve has converged it'll have to, so:
    1243      142386 :           res += _mgd.basis_species_charge[i] * (_bulk_moles_old[i] + mole_additions(i)) /
    1244             :                  _mgd.basis_species_charge[basis_i];
    1245             :         }
    1246             :         else
    1247             :         {
    1248             :           // do not know the bulk moles of this species: use the current value for molality and
    1249             :           // kin_moles.  Note, there is no mole_additions here since physically any mole_additions
    1250             :           // get instantly get soaked up by the fixed activity (or fixed molality, etc), and
    1251             :           // mathematically when we eventually come to add mole_additions to the bulk_moles_old
    1252             :           // (via addtoBulkMoles, for instance) we immediately return without adding stuff.  End
    1253             :           // result: charge-neutrality should not depend on mole_additions for fixed-activity
    1254             :           // (molality, etc) species.
    1255       20211 :           res += _mgd.basis_species_charge[i] * computeBulkFromMolalities(i) /
    1256       20211 :                  _mgd.basis_species_charge[basis_i];
    1257             :         }
    1258             :       }
    1259             :     }
    1260             :     else
    1261      157688 :       res += -(_bulk_moles_old[basis_i] + mole_additions(basis_i)) +
    1262      157688 :              _basis_molality[0] * _basis_molality[basis_i];
    1263    14338926 :     for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
    1264    14095125 :       res += _basis_molality[0] * _mgd.eqm_stoichiometry(eqm_j, basis_i) * _eqm_molality[eqm_j];
    1265      269226 :     for (unsigned kin = 0; kin < _num_kin; ++kin)
    1266       25425 :       res += _mgd.kin_stoichiometry(kin, basis_i) * _kin_moles[kin];
    1267      243801 :     return res;
    1268             :   }
    1269        9813 :   else if (algebraic_ind <
    1270        9813 :            _num_basis_in_algebraic_system + _num_surface_pot) // residual for surface potential
    1271             :   {
    1272        3482 :     const unsigned sp = algebraic_ind - _num_basis_in_algebraic_system;
    1273        3482 :     Real res = surfacePotPrefactor(sp) * (_surface_pot_expr[sp] - 1.0 / _surface_pot_expr[sp]);
    1274       44145 :     for (unsigned j = 0; j < _num_eqm; ++j)
    1275       40663 :       if (_mgd.surface_sorption_related[j] && _mgd.surface_sorption_number[j] == sp)
    1276       18569 :         res += _basis_molality[0] * _mgd.eqm_species_charge[j] * _eqm_molality[j];
    1277        3482 :     return res;
    1278             :   }
    1279             : 
    1280             :   // else: residual for kinetic
    1281        6331 :   const unsigned kin = algebraic_ind - _num_basis_in_algebraic_system - _num_surface_pot;
    1282        6331 :   Real res = _kin_moles[kin] - (_kin_moles_old[kin] + mole_additions(_num_basis + kin));
    1283        6331 :   return res;
    1284             : }
    1285             : 
    1286             : void
    1287       40594 : GeochemicalSystem::computeJacobian(const DenseVector<Real> & res,
    1288             :                                    DenseMatrix<Real> & jac,
    1289             :                                    const DenseVector<Real> & mole_additions,
    1290             :                                    const DenseMatrix<Real> & dmole_additions) const
    1291             : {
    1292             :   /* To the reader: yes, this is awfully complicated.  The best way of understanding it is to very
    1293             :    * slowly go through the residual calculation and compute the derivatives by hand.  It is quite
    1294             :    * well tested, but it'd be great if you could add more tests!
    1295             :    */
    1296       40594 :   if (res.size() != _num_in_algebraic_system)
    1297           1 :     mooseError(
    1298           1 :         "Jacobian: residual size must be ", _num_in_algebraic_system, " but it is ", res.size());
    1299       40593 :   if (mole_additions.size() != _num_basis + _num_kin)
    1300           1 :     mooseError("Jacobian: the increment in mole numbers (mole_additions) needs to be of size ",
    1301           1 :                _num_basis,
    1302             :                " + ",
    1303           1 :                _num_kin,
    1304             :                " but it is of size ",
    1305           1 :                mole_additions.size());
    1306       40592 :   if (!(dmole_additions.n() == _num_basis + _num_kin &&
    1307             :         dmole_additions.m() == _num_basis + _num_kin))
    1308           2 :     mooseError("Jacobian: the derivative of mole additions (dmole_additions) needs to be of size ",
    1309           2 :                _num_basis + _num_kin,
    1310             :                "x",
    1311           2 :                _num_basis + _num_kin,
    1312             :                " but it is of size ",
    1313           2 :                dmole_additions.m(),
    1314             :                "x",
    1315           2 :                dmole_additions.n());
    1316             :   /*
    1317             : Note that in this function we make use of the fact that species can only be in the algebraic
    1318             : system if their molality is unknown (or in the case of water, the mass of solvent mass is
    1319             : unknown).  This means that the molality of the equilibrium species depends on
    1320             : (activity_coefficient * basis molality)^stoi, so that the derivative is quite simple.  Eg, we
    1321             : don't have to worry about derivatives with respect to fixed-activity things, or fixed fugacity.
    1322             : Also, note that the constructor and the various "set" methods of this class enforce molality > 0,
    1323             : so there are no division-by-zero problems.  Also, note that we never compute derivatives of the
    1324             : activity coefficients, or the activity of water with respect to the molalities, as they are
    1325             : assumed to be quite small.  Finally, the surface_pot_expr will also always be positive.
    1326             :   */
    1327             :   // correctly size and zero
    1328       40590 :   jac.resize(_num_in_algebraic_system, _num_in_algebraic_system);
    1329       40590 :   const Real nw = _basis_molality[0];
    1330             : 
    1331             :   // jac(a, b) = d(R_a) / d(m_b), where a corresponds to a molality
    1332      225465 :   for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
    1333             :   {
    1334      184875 :     const unsigned basis_of_a = _basis_index[a];
    1335     1476670 :     for (unsigned b = 0; b < _num_basis_in_algebraic_system; ++b)
    1336             :     {
    1337     1291795 :       const unsigned basis_of_b = _basis_index[b];
    1338             : 
    1339             :       // contribution from mole_additions
    1340     1291795 :       if (basis_of_a != _charge_balance_basis_index)
    1341     1106920 :         jac(a, b) -= dmole_additions(basis_of_a, basis_of_b);
    1342             :       else
    1343             :       {
    1344     1859238 :         for (unsigned i = 0; i < _num_basis; ++i)
    1345             :         {
    1346     1674363 :           if (i == _charge_balance_basis_index)
    1347      184875 :             continue;
    1348     1489488 :           else if (_mgd.basis_species_charge[i] ==
    1349             :                    0.0) // certainly includes water, minerals and gases
    1350      540313 :             continue;
    1351      949175 :           else if (_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1352      887845 :             jac(a, b) += _mgd.basis_species_charge[i] * dmole_additions(i, basis_of_b) /
    1353      887845 :                          _mgd.basis_species_charge[basis_of_a];
    1354             :         }
    1355             :       }
    1356             : 
    1357             :       // contribution from explicit nw, in case where mass of solvent water is an unknown
    1358     1291795 :       if (basis_of_b == 0)
    1359             :       {
    1360      122647 :         if (basis_of_a != _charge_balance_basis_index)
    1361             :         {
    1362             :           // use a short-cut here: dR/dnw = m + sum_eqm[stoi*mol] = (R + bulk + addition - kin)/nw
    1363       90098 :           Real numerator = res(a) + _bulk_moles_old[basis_of_a] + mole_additions(basis_of_a);
    1364      102685 :           for (unsigned kin = 0; kin < _num_kin; ++kin)
    1365       12587 :             numerator -= _mgd.kin_stoichiometry(kin, basis_of_a) * _kin_moles[kin];
    1366       90098 :           jac(a, 0) += numerator / nw;
    1367             :         }
    1368             :         else
    1369             :         {
    1370      198478 :           for (unsigned i = 0; i < _num_basis; ++i)
    1371             :           {
    1372      165929 :             if (i == _charge_balance_basis_index)
    1373       32549 :               continue;
    1374      133380 :             else if (_mgd.basis_species_charge[i] ==
    1375             :                      0.0) // certainly includes water, minerals and gases
    1376       70422 :               continue;
    1377       62958 :             else if (_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1378       52336 :               continue;
    1379             :             else
    1380             :             {
    1381       10622 :               Real molal = _basis_molality[i];
    1382       49951 :               for (unsigned j = 0; j < _num_eqm; ++j)
    1383       39329 :                 molal += _mgd.eqm_stoichiometry(j, i) * _eqm_molality[j];
    1384       10622 :               jac(a, 0) +=
    1385       10622 :                   _mgd.basis_species_charge[i] * molal / _mgd.basis_species_charge[basis_of_a];
    1386             :             }
    1387             :           }
    1388       32549 :           jac(a, 0) += _basis_molality[basis_of_a];
    1389      511886 :           for (unsigned j = 0; j < _num_eqm; ++j)
    1390      479337 :             jac(a, 0) += _mgd.eqm_stoichiometry(j, basis_of_a) * _eqm_molality[j];
    1391             :         }
    1392             :       }
    1393             : 
    1394             :       // contribution from molality of basis_of_b
    1395             :       if (basis_of_b != 0)
    1396             :       {
    1397     1169148 :         if (a == b)
    1398      152326 :           jac(a, b) += nw; // remember b != 0
    1399   104175398 :         for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
    1400             :         {
    1401   103006250 :           jac(a, b) += nw * _mgd.eqm_stoichiometry(eqm_j, basis_of_a) * _eqm_molality[eqm_j] *
    1402   103006250 :                        _mgd.eqm_stoichiometry(eqm_j, basis_of_b) / _basis_molality[basis_of_b];
    1403             :         }
    1404     1169148 :         if (basis_of_a == _charge_balance_basis_index)
    1405             :         {
    1406             :           // additional terms from the charge-balance additions
    1407     1660760 :           for (unsigned i = 0; i < _num_basis; ++i)
    1408             :           {
    1409     1508434 :             if (i == _charge_balance_basis_index)
    1410      152326 :               continue;
    1411     1356108 :             else if (_mgd.basis_species_charge[i] ==
    1412             :                      0.0) // certainly includes water, minerals and gases
    1413      469891 :               continue;
    1414      886217 :             else if (_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1415      835509 :               continue;
    1416             :             else
    1417             :             {
    1418             :               const Real prefactor =
    1419       50708 :                   _mgd.basis_species_charge[i] * nw / _mgd.basis_species_charge[basis_of_a];
    1420       50708 :               if (i == basis_of_b)
    1421           0 :                 jac(a, b) += prefactor;
    1422     3068814 :               for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
    1423             :               {
    1424     3018106 :                 jac(a, b) += prefactor * _mgd.eqm_stoichiometry(eqm_j, i) * _eqm_molality[eqm_j] *
    1425     3018106 :                              _mgd.eqm_stoichiometry(eqm_j, basis_of_b) /
    1426     3018106 :                              _basis_molality[basis_of_b];
    1427             :               }
    1428             :             }
    1429             :           }
    1430             :         }
    1431             :       }
    1432             :     }
    1433             :   }
    1434             : 
    1435             :   // jac(a, b) = d(R_a) / d(surface_pot), where a corresponds to a molality
    1436      225465 :   for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
    1437             :   {
    1438      184875 :     const unsigned basis_of_a = _basis_index[a];
    1439      198812 :     for (unsigned s = 0; s < _num_surface_pot; ++s)
    1440             :     {
    1441       13937 :       const unsigned b = s + _num_basis_in_algebraic_system;
    1442             :       // derivative of nw * _mgd.eqm_stoichiometry(eqm_j, basis_i) * _eqm_molality[eqm_j],
    1443             :       // where _eqm_molality = (_surface_pot_expr)^(2 * charge) * stuff
    1444      200875 :       for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
    1445      186938 :         if (_mgd.surface_sorption_related[eqm_j] && _mgd.surface_sorption_number[eqm_j] == s)
    1446       82130 :           jac(a, b) += nw * _mgd.eqm_stoichiometry(eqm_j, basis_of_a) * 2.0 *
    1447       82130 :                        _mgd.eqm_species_charge[eqm_j] * _eqm_molality[eqm_j] /
    1448       82130 :                        _surface_pot_expr[_mgd.surface_sorption_number[eqm_j]];
    1449             :     }
    1450             :   }
    1451             : 
    1452             :   // jac(a, b) = d(R_a) / d(_kin_moles) where a corresponds to a molality
    1453      225465 :   for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
    1454             :   {
    1455      184875 :     const unsigned basis_of_a = _basis_index[a];
    1456             : 
    1457             :     // contribution from mole_additions
    1458      205764 :     for (unsigned kin = 0; kin < _num_kin; ++kin)
    1459             :     {
    1460       20889 :       const unsigned ind = _num_basis + kin;
    1461       20889 :       const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kin;
    1462       20889 :       if (basis_of_a != _charge_balance_basis_index)
    1463       15638 :         jac(a, b) -= dmole_additions(basis_of_a, ind);
    1464             :       else
    1465             :       {
    1466       47935 :         for (unsigned i = 0; i < _num_basis; ++i)
    1467             :         {
    1468       42684 :           if (i == _charge_balance_basis_index)
    1469        5251 :             continue;
    1470       37433 :           else if (_mgd.basis_species_charge[i] ==
    1471             :                    0.0) // certainly includes water, minerals and gases
    1472       26399 :             continue;
    1473       11034 :           else if (_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1474        9466 :             jac(a, b) += _mgd.basis_species_charge[i] * dmole_additions(i, ind) /
    1475        9466 :                          _mgd.basis_species_charge[basis_of_a];
    1476             :         }
    1477             :       }
    1478             :     }
    1479             : 
    1480             :     // contribution from sum_kin[stoi*kin_moles]
    1481      205764 :     for (unsigned kin = 0; kin < _num_kin; ++kin)
    1482             :     {
    1483       20889 :       const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kin;
    1484       20889 :       jac(a, b) += _mgd.kin_stoichiometry(kin, basis_of_a);
    1485             :     }
    1486             : 
    1487             :     // special additional contribution for charge-balance from kinetic stuff
    1488      184875 :     if (basis_of_a == _charge_balance_basis_index)
    1489             :     {
    1490      295338 :       for (unsigned i = 0; i < _num_basis; ++i)
    1491             :       {
    1492      254748 :         if (i == _charge_balance_basis_index)
    1493       40590 :           continue;
    1494      214158 :         else if (_mgd.basis_species_charge[i] ==
    1495             :                  0.0) // certainly includes water, minerals and gases
    1496       98082 :           continue;
    1497      116076 :         else if (_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1498      100370 :           continue;
    1499             :         else
    1500             :         {
    1501             :           const Real prefactor =
    1502       15706 :               _mgd.basis_species_charge[i] / _mgd.basis_species_charge[basis_of_a];
    1503       17274 :           for (unsigned kin = 0; kin < _num_kin; ++kin)
    1504             :           {
    1505        1568 :             const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kin;
    1506        1568 :             jac(a, b) += prefactor * _mgd.kin_stoichiometry(kin, i);
    1507             :           }
    1508             :         }
    1509             :       }
    1510             :     }
    1511             :   }
    1512             : 
    1513             :   // jac(a, b) = d(R_a) / d(variable_b) where a corresponds to a surface potential
    1514       43662 :   for (unsigned s = 0; s < _num_surface_pot; ++s)
    1515             :   {
    1516        3072 :     const unsigned a = s + _num_basis_in_algebraic_system;
    1517             : 
    1518       17009 :     for (unsigned b = 0; b < _num_basis_in_algebraic_system; ++b)
    1519             :     {
    1520             :       // derivative of nw * _mgd.eqm_species_charge[j] * _eqm_molality[j];
    1521       13937 :       const unsigned basis_of_b = _basis_index[b];
    1522       13937 :       if (basis_of_b == 0) // special case: mass of solvent water is an unknown
    1523             :       {
    1524       14588 :         for (unsigned j = 0; j < _num_eqm; ++j)
    1525       13125 :           if (_mgd.surface_sorption_related[j] && _mgd.surface_sorption_number[j] == s)
    1526        5843 :             jac(a, b) += _mgd.eqm_species_charge[j] * _eqm_molality[j];
    1527             :       }
    1528             :       else
    1529             :       {
    1530      186287 :         for (unsigned j = 0; j < _num_eqm; ++j)
    1531      173813 :           if (_mgd.surface_sorption_related[j] && _mgd.surface_sorption_number[j] == s)
    1532       76287 :             jac(a, b) += nw * _mgd.eqm_species_charge[j] * _eqm_molality[j] *
    1533       76287 :                          _mgd.eqm_stoichiometry(j, basis_of_b) / _basis_molality[basis_of_b];
    1534             :       }
    1535             :     }
    1536             : 
    1537        3072 :     const Real coef = surfacePotPrefactor(s);
    1538             :     // derivative of coef * (x - 1/x) wrt x, where x = _surface_pot_expr
    1539        3072 :     jac(a, a) += coef * (1.0 + 1.0 / std::pow(_surface_pot_expr[s], 2.0));
    1540             :     // derivative of nw * _mgd.eqm_species_charge[j] * _eqm_molality[j];
    1541             :     // where _eqm_molality = (_surface_pot_expr)^(2 * charge) * stuff
    1542       37957 :     for (unsigned j = 0; j < _num_eqm; ++j)
    1543       34885 :       if (_mgd.surface_sorption_related[j] && _mgd.surface_sorption_number[j] == s)
    1544       16053 :         jac(a, a) += nw * std::pow(_mgd.eqm_species_charge[j], 2.0) * 2.0 * _eqm_molality[j] /
    1545             :                      _surface_pot_expr[s];
    1546             :   }
    1547             : 
    1548             :   // jac(a, b) = d(R_a) / d(variable_b) where a corresponds to a kinetic species
    1549       45841 :   for (unsigned kin = 0; kin < _num_kin; ++kin)
    1550             :   {
    1551        5251 :     const unsigned a = _num_basis_in_algebraic_system + _num_surface_pot + kin;
    1552        5251 :     jac(a, a) += 1.0; // deriv wrt _kin_moles[kin]
    1553             : 
    1554             :     // contribution from mole_additions
    1555        5251 :     const unsigned ind_of_addition = _num_basis + kin;
    1556       26140 :     for (unsigned b = 0; b < _num_basis_in_algebraic_system; ++b)
    1557             :     {
    1558       20889 :       const unsigned basis_of_b = _basis_index[b];
    1559       20889 :       jac(a, b) -= dmole_additions(ind_of_addition, basis_of_b);
    1560             :     }
    1561       10522 :     for (unsigned kinp = 0; kinp < _num_kin; ++kinp)
    1562             :     {
    1563        5271 :       const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kinp;
    1564        5271 :       const unsigned indp = _num_basis + kinp;
    1565        5271 :       jac(a, b) -= dmole_additions(ind_of_addition, indp);
    1566             :     }
    1567             :   }
    1568       40590 : }
    1569             : 
    1570             : const ModelGeochemicalDatabase &
    1571      664963 : GeochemicalSystem::getModelGeochemicalDatabase() const
    1572             : {
    1573      664963 :   return _mgd;
    1574             : }
    1575             : 
    1576             : void
    1577      100322 : GeochemicalSystem::computeFreeMineralMoles(std::vector<Real> & basis_molality) const
    1578             : {
    1579             : 
    1580      100322 :   const Real nw = _basis_molality[0];
    1581      846125 :   for (unsigned i = 0; i < _num_basis; ++i)
    1582      745803 :     if (_mgd.basis_species_mineral[i])
    1583             :     {
    1584      122448 :       if (_constraint_meaning[i] == ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES)
    1585        6279 :         basis_molality[i] = _constraint_value[i];
    1586             :       else
    1587             :       {
    1588      116169 :         basis_molality[i] = _bulk_moles_old[i];
    1589     6611004 :         for (unsigned j = 0; j < _num_eqm; ++j)
    1590     6494835 :           basis_molality[i] -= nw * _mgd.eqm_stoichiometry(j, i) * _eqm_molality[j];
    1591      153625 :         for (unsigned kin = 0; kin < _num_kin; ++kin)
    1592       37456 :           basis_molality[i] -= _mgd.kin_stoichiometry(kin, i) * _kin_moles[kin];
    1593             :       }
    1594             :     }
    1595      100322 : }
    1596             : 
    1597             : std::vector<Real>
    1598        5128 : GeochemicalSystem::getSaturationIndices() const
    1599             : {
    1600        5128 :   std::vector<Real> si(_num_eqm);
    1601      100330 :   for (unsigned j = 0; j < _num_eqm; ++j)
    1602       95202 :     if (_mgd.eqm_species_mineral[j])
    1603        4695 :       si[j] = log10ActivityProduct(j) - _eqm_log10K[j];
    1604             :     else
    1605       90507 :       si[j] = 0.0;
    1606        5128 :   return si;
    1607             : }
    1608             : 
    1609             : void
    1610         304 : GeochemicalSystem::performSwap(unsigned swap_out_of_basis, unsigned swap_into_basis)
    1611             : {
    1612         304 :   if (swap_out_of_basis == 0)
    1613           1 :     mooseException("GeochemicalSystem: attempting to swap out water and replace it by ",
    1614             :                    _mgd.eqm_species_name[swap_into_basis],
    1615             :                    ".  This could be because the algorithm would like to "
    1616             :                    "swap out the charge-balance species, in which case you should choose a "
    1617             :                    "different charge-balance species");
    1618         303 :   if (swap_out_of_basis == _charge_balance_basis_index)
    1619           1 :     mooseException("GeochemicalSystem: attempting to swap the charge-balance species out of "
    1620             :                    "the basis");
    1621         302 :   if (_mgd.basis_species_gas[swap_out_of_basis])
    1622           1 :     mooseException("GeochemicalSystem: attempting to swap a gas out of the basis");
    1623         301 :   if (_mgd.eqm_species_gas[swap_into_basis])
    1624           1 :     mooseException("GeochemicalSystem: attempting to swap a gas into the basis");
    1625             :   // perform the swap
    1626         300 :   performSwapNoCheck(swap_out_of_basis, swap_into_basis);
    1627         300 : }
    1628             : 
    1629             : void
    1630         406 : GeochemicalSystem::performSwapNoCheck(unsigned swap_out_of_basis, unsigned swap_into_basis)
    1631             : {
    1632         406 :   DenseVector<Real> bm = _bulk_moles_old;
    1633         406 :   _swapper.performSwap(_mgd, bm, swap_out_of_basis, swap_into_basis);
    1634             : 
    1635             :   // the swap_into_basis species now has fixed bulk moles irrespective of what the
    1636             :   // swap_out_of_basis species had fixed
    1637         406 :   _constraint_meaning[swap_out_of_basis] = ConstraintMeaningEnum::MOLES_BULK_SPECIES;
    1638             : 
    1639             :   // the bulk moles will have changed for many components.  The _bulk_moles_old values will be
    1640             :   // set in computeConsistentConfiguration, below
    1641        5472 :   for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
    1642        5066 :     if (_constraint_meaning[basis_i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES ||
    1643             :         _constraint_meaning[basis_i] == ConstraintMeaningEnum::MOLES_BULK_WATER)
    1644             :     {
    1645        4515 :       _constraint_value[basis_i] = bm(basis_i);
    1646        4515 :       _original_constraint_value[basis_i] = bm(basis_i);
    1647             :     }
    1648             : 
    1649             :   // In the following, the molalities are carefully swapped so that the configuration stays
    1650             :   // reasonably close to the configuration prior to the swap.  This may be advantageous as it
    1651             :   // initializes the Newton process with a better starting guess than what might have been used
    1652             :   Real molality_of_species_just_swapped_in =
    1653         406 :       _eqm_molality[swap_into_basis]; // is positive, or zero iff (mineral or gas)
    1654         406 :   if (_mgd.basis_species_mineral[swap_out_of_basis] || _mgd.basis_species_gas[swap_out_of_basis] ||
    1655             :       _eqm_molality[swap_into_basis] == 0.0)
    1656             :   {
    1657             :     // the species just swapped in is a mineral or a gas, so its equilibium molality is
    1658             :     // undefined: make a guess for its molality
    1659         247 :     molality_of_species_just_swapped_in =
    1660         431 :         std::max(_min_initial_molality, 0.9 * bm(swap_out_of_basis));
    1661             :   }
    1662             :   Real molality_of_species_just_swapped_out =
    1663         406 :       _basis_molality[swap_out_of_basis]; // can be negative if a consumed mineral
    1664         406 :   _basis_molality[swap_out_of_basis] = molality_of_species_just_swapped_in;
    1665         406 :   _eqm_molality[swap_into_basis] =
    1666         406 :       std::max(0.0, molality_of_species_just_swapped_out); // this gets recalculated in
    1667             :                                                            // computeConsistentConfiguration, below
    1668             : 
    1669             :   // depending if minerals were swapped in or out of the basis, the known activity may have
    1670             :   // changed
    1671         406 :   buildKnownBasisActivities(_basis_activity_known, _basis_activity);
    1672             : 
    1673             :   // the equilibrium constants will have changed due to the swap
    1674         406 :   buildTemperatureDependentQuantities(_temperature);
    1675             : 
    1676             :   // due to re-orderings in mgd, the charge-balance basis index might have changed
    1677         406 :   _charge_balance_basis_index = _mgd.basis_species_index.at(_charge_balance_species);
    1678             :   // charge balance might be able to be performed easily
    1679         406 :   enforceChargeBalanceIfSimple(_constraint_value, _bulk_moles_old);
    1680             : 
    1681             :   // the algebraic system has probably changed
    1682         406 :   buildAlgebraicInfo(_in_algebraic_system,
    1683         406 :                      _num_basis_in_algebraic_system,
    1684         406 :                      _num_in_algebraic_system,
    1685         406 :                      _algebraic_index,
    1686         406 :                      _basis_index);
    1687             : 
    1688             :   // finally compute a consistent configuration, based on the basis molalities, etc, above
    1689         406 :   computeConsistentConfiguration();
    1690         406 : }
    1691             : 
    1692             : unsigned
    1693        2389 : GeochemicalSystem::getNumInBasis() const
    1694             : {
    1695        2389 :   return _num_basis;
    1696             : }
    1697             : 
    1698             : unsigned
    1699         528 : GeochemicalSystem::getNumInEquilibrium() const
    1700             : {
    1701         528 :   return _num_eqm;
    1702             : }
    1703             : 
    1704             : void
    1705           9 : GeochemicalSystem::setChargeBalanceSpecies(unsigned new_charge_balance_index)
    1706             : {
    1707             :   // because the original mole number of the charge balance species may have been changed due to
    1708             :   // enforcing charge balance:
    1709           9 :   _constraint_value[_charge_balance_basis_index] =
    1710           9 :       _original_constraint_value[_charge_balance_basis_index];
    1711           9 :   _bulk_moles_old[_charge_balance_basis_index] = _constraint_value[_charge_balance_basis_index];
    1712             :   // now change the charge balance info
    1713           9 :   _charge_balance_basis_index = new_charge_balance_index;
    1714           9 :   _charge_balance_species = _mgd.basis_species_name[_charge_balance_basis_index];
    1715             :   // enforce charge-balance if easily possible
    1716           9 :   enforceChargeBalanceIfSimple(_constraint_value, _bulk_moles_old);
    1717           9 : }
    1718             : 
    1719             : bool
    1720       40580 : GeochemicalSystem::alterChargeBalanceSpecies(Real threshold_molality)
    1721             : {
    1722       40580 :   if (_basis_molality[_charge_balance_basis_index] > threshold_molality)
    1723             :     return false;
    1724             :   unsigned best_species_opp_sign = 0;
    1725             :   unsigned best_species_same_sign = 0;
    1726             :   Real best_molality_opp_sign = 0.0;
    1727             :   Real best_molality_same_sign = 0.0;
    1728        1965 :   for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
    1729             :   {
    1730        1761 :     if (basis_i == _charge_balance_basis_index)
    1731         204 :       continue;
    1732        1557 :     else if (_constraint_meaning[basis_i] != ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    1733         490 :       continue;
    1734        1067 :     else if (_mgd.basis_species_charge[basis_i] == 0.0)
    1735         203 :       continue;
    1736         864 :     else if (_basis_molality[basis_i] <= threshold_molality)
    1737         772 :       continue;
    1738             :     // we know basis_i is a charged species with set bulk moles and high molality
    1739          92 :     if (_mgd.basis_species_charge[basis_i] *
    1740             :             _mgd.basis_species_charge[_charge_balance_basis_index] <
    1741             :         0.0)
    1742             :     {
    1743             :       // charge of opposite sign
    1744          78 :       if (_basis_molality[basis_i] > best_molality_opp_sign)
    1745             :       {
    1746             :         best_species_opp_sign = basis_i;
    1747             :         best_molality_opp_sign = _basis_molality[basis_i];
    1748             :       }
    1749             :     }
    1750             :     else
    1751             :     {
    1752             :       // charge of same sign
    1753          14 :       if (_basis_molality[basis_i] > best_molality_same_sign)
    1754             :       {
    1755             :         best_species_same_sign = basis_i;
    1756             :         best_molality_same_sign = _basis_molality[basis_i];
    1757             :       }
    1758             :     }
    1759             :   }
    1760         204 :   if (best_species_opp_sign != 0)
    1761             :   {
    1762             :     // this is preferred over the same-sign version
    1763           8 :     setChargeBalanceSpecies(best_species_opp_sign);
    1764           8 :     return true;
    1765             :   }
    1766         196 :   else if (best_species_same_sign != 0)
    1767             :   {
    1768             :     // this is not preferred, but is better than no charge-balance species change
    1769           0 :     setChargeBalanceSpecies(best_species_same_sign);
    1770           0 :     return true;
    1771             :   }
    1772             :   return false;
    1773             : }
    1774             : 
    1775             : bool
    1776           1 : GeochemicalSystem::revertToOriginalChargeBalanceSpecies()
    1777             : {
    1778           1 :   if (_mgd.basis_species_index.find(_original_charge_balance_species) ==
    1779           1 :       _mgd.basis_species_index.end())
    1780             :     return false; // original charge-balance species no longer in basis
    1781           1 :   const unsigned original_index = _mgd.basis_species_index.at(_original_charge_balance_species);
    1782           1 :   if (original_index == _charge_balance_basis_index)
    1783             :     return false; // current charge-balance species is the original
    1784           1 :   setChargeBalanceSpecies(original_index);
    1785           1 :   return true;
    1786             : }
    1787             : 
    1788             : Real
    1789         321 : GeochemicalSystem::getIonicStrength() const
    1790             : {
    1791         321 :   return _is.ionicStrength(_mgd, _basis_molality, _eqm_molality, _kin_moles);
    1792             : }
    1793             : 
    1794             : Real
    1795         316 : GeochemicalSystem::getStoichiometricIonicStrength() const
    1796             : {
    1797         316 :   return _is.stoichiometricIonicStrength(_mgd, _basis_molality, _eqm_molality, _kin_moles);
    1798             : }
    1799             : 
    1800             : Real
    1801        7022 : GeochemicalSystem::surfacePotPrefactor(unsigned sp) const
    1802             : {
    1803        7022 :   return 0.5 * _sorbing_surface_area[sp] / GeochemistryConstants::FARADAY *
    1804       14044 :          std::sqrt(8.0 * GeochemistryConstants::GAS_CONSTANT *
    1805        7022 :                    (_temperature + GeochemistryConstants::CELSIUS_TO_KELVIN) *
    1806        7022 :                    GeochemistryConstants::PERMITTIVITY_FREE_SPACE *
    1807        7022 :                    GeochemistryConstants::DIELECTRIC_CONSTANT_WATER *
    1808             :                    GeochemistryConstants::DENSITY_WATER *
    1809        7022 :                    _is.ionicStrength(_mgd, _basis_molality, _eqm_molality, _kin_moles));
    1810             : }
    1811             : 
    1812             : Real
    1813         482 : GeochemicalSystem::getSurfacePotential(unsigned sp) const
    1814             : {
    1815         482 :   if (sp >= _num_surface_pot)
    1816           2 :     mooseError("Cannot retrieve the surface potential for surface ",
    1817             :                sp,
    1818             :                " since there are only ",
    1819           2 :                _num_surface_pot,
    1820             :                " surfaces involved in surface complexation");
    1821         480 :   return -2.0 * GeochemistryConstants::GAS_CONSTANT *
    1822         480 :          (_temperature + GeochemistryConstants::CELSIUS_TO_KELVIN) /
    1823         480 :          GeochemistryConstants::FARADAY * std::log(_surface_pot_expr[sp]);
    1824             : }
    1825             : 
    1826             : Real
    1827         470 : GeochemicalSystem::getSurfaceCharge(unsigned sp) const
    1828             : {
    1829         470 :   if (sp >= _num_surface_pot)
    1830           2 :     mooseError("Cannot retrieve the surface charge for surface ",
    1831             :                sp,
    1832             :                " since there are only ",
    1833           2 :                _num_surface_pot,
    1834             :                " surfaces involved in surface complexation");
    1835             :   // pre = mol of charge per square metre.  To convert to Coulombs/m^2:
    1836         468 :   const Real pre = surfacePotPrefactor(sp) / _sorbing_surface_area[sp] *
    1837         468 :                    (-_surface_pot_expr[sp] + 1.0 / _surface_pot_expr[sp]);
    1838         468 :   return pre * GeochemistryConstants::FARADAY;
    1839             : }
    1840             : 
    1841             : void
    1842       47946 : GeochemicalSystem::computeSorbingSurfaceArea(std::vector<Real> & sorbing_surface_area) const
    1843             : {
    1844       51472 :   for (unsigned sp = 0; sp < _num_surface_pot; ++sp)
    1845             :   {
    1846        3526 :     sorbing_surface_area[sp] = _mgd.surface_sorption_area[sp];
    1847             :     if (_mgd.basis_species_index.count(_mgd.surface_sorption_name[sp]) == 1)
    1848             :     {
    1849        2971 :       const unsigned basis_ind = _mgd.basis_species_index.at(_mgd.surface_sorption_name[sp]);
    1850             :       const Real grams =
    1851        2971 :           _mgd.basis_species_molecular_weight[basis_ind] * _basis_molality[basis_ind];
    1852        2971 :       sorbing_surface_area[sp] *= grams;
    1853             :     }
    1854             :   }
    1855       47946 : }
    1856             : 
    1857             : const std::vector<Real> &
    1858          60 : GeochemicalSystem::getSorbingSurfaceArea() const
    1859             : {
    1860          60 :   return _sorbing_surface_area;
    1861             : }
    1862             : 
    1863             : Real
    1864        8271 : GeochemicalSystem::getTemperature() const
    1865             : {
    1866        8271 :   return _temperature;
    1867             : }
    1868             : 
    1869             : void
    1870         470 : GeochemicalSystem::setTemperature(Real temperature)
    1871             : {
    1872         470 :   _temperature = temperature;
    1873         470 :   buildTemperatureDependentQuantities(_temperature);
    1874         470 :   _gac.setInternalParameters(_temperature, _mgd, _basis_molality, _eqm_molality, _kin_moles);
    1875         470 : }
    1876             : 
    1877             : void
    1878          28 : GeochemicalSystem::setSolventMassAndFreeMolalityAndMineralMolesAndSurfacePotsAndKineticMoles(
    1879             :     const std::vector<std::string> & names,
    1880             :     const std::vector<Real> & values,
    1881             :     const std::vector<bool> & constraints_from_molalities)
    1882             : {
    1883             :   // assume temperature-dependent quantities have been built during instantiation
    1884             :   // assume algebraic info has been built during instantiation
    1885             : 
    1886             :   /*
    1887             :    * STEP 0: Check sizes are correct
    1888             :    */
    1889          28 :   const unsigned num_names = names.size();
    1890          28 :   if (num_names != values.size())
    1891           1 :     mooseError("When setting all molalities, names and values must have same size");
    1892          27 :   if (num_names != _num_basis + _num_eqm + _num_surface_pot + _num_kin)
    1893           2 :     mooseError("When setting all molalities, values must be provided for every species and surface "
    1894             :                "potentials");
    1895          25 :   if (constraints_from_molalities.size() != _num_basis)
    1896           1 :     mooseError("constraints_from_molalities has size ",
    1897           1 :                constraints_from_molalities.size(),
    1898             :                " while number of basis species is ",
    1899           1 :                _num_basis);
    1900             : 
    1901             :   /*
    1902             :    * STEP 1: Read values into _basis_molality, _eqm_molality and _surface_pot_expr and
    1903             :    * _kin_moles
    1904             :    */
    1905             :   // The is no guarantee is made that the user-supplied molalities are "good", except they must
    1906             :   // be non-negative. There are lots of "finds" in the loops below, which is slow, but it
    1907             :   // ensures all species are given molalities.  This method is designed to be called only every
    1908             :   // once-in-a-while (eg, at the start of a simulation)
    1909         176 :   for (const auto & name_index : _mgd.basis_species_index)
    1910             :   {
    1911             :     const unsigned ind =
    1912         156 :         std::distance(names.begin(), std::find(names.begin(), names.end(), name_index.first));
    1913         156 :     if (ind < num_names)
    1914             :     {
    1915         155 :       if (_mgd.basis_species_gas[name_index.second])
    1916             :       {
    1917           8 :         if (values[ind] != 0.0)
    1918           1 :           mooseError("Molality for gas ",
    1919             :                      name_index.first,
    1920             :                      " cannot be ",
    1921             :                      values[ind],
    1922             :                      ": it must be zero");
    1923             :         else
    1924           7 :           _basis_molality[name_index.second] = values[ind];
    1925             :       }
    1926         147 :       else if (_mgd.basis_species_mineral[name_index.second])
    1927             :       {
    1928          15 :         if (values[ind] < 0.0)
    1929           1 :           mooseError("Molality for mineral ",
    1930             :                      name_index.first,
    1931             :                      " cannot be ",
    1932             :                      values[ind],
    1933             :                      ": it must be non-negative");
    1934             :         else
    1935          14 :           _basis_molality[name_index.second] = values[ind];
    1936             :       }
    1937         132 :       else if (values[ind] <= 0.0)
    1938           1 :         mooseError("Molality for species ",
    1939             :                    name_index.first,
    1940             :                    " cannot be ",
    1941             :                    values[ind],
    1942             :                    ": it must be positive");
    1943             :       else
    1944         131 :         _basis_molality[name_index.second] = values[ind];
    1945             :     }
    1946             :     else
    1947           1 :       mooseError("Molality (or free mineral moles, etc - whatever is appropriate) for species ",
    1948             :                  name_index.first,
    1949             :                  " needs to be provided when setting all molalities");
    1950             :   }
    1951         486 :   for (const auto & name_index : _mgd.eqm_species_index)
    1952             :   {
    1953             :     const unsigned ind =
    1954         470 :         std::distance(names.begin(), std::find(names.begin(), names.end(), name_index.first));
    1955         470 :     if (ind < num_names)
    1956             :     {
    1957         469 :       if (_mgd.eqm_species_gas[name_index.second] || _mgd.eqm_species_mineral[name_index.second])
    1958          30 :         _eqm_molality[name_index.second] =
    1959             :             0.0; // Note: explicitly setting to zero, irrespective of user input.  The reason
    1960             :                  // for doing this is that during a restore, a basis species with positive
    1961             :                  // molality could become a secondary species, which should have zero molality
    1962         439 :       else if (values[ind] < 0.0)
    1963           3 :         mooseError("Molality for species ",
    1964             :                    name_index.first,
    1965             :                    " cannot be ",
    1966             :                    values[ind],
    1967             :                    ": it must be non-negative");
    1968             :       else
    1969         436 :         _eqm_molality[name_index.second] = values[ind];
    1970             :     }
    1971             :     else
    1972           1 :       mooseError("Molality for species ",
    1973             :                  name_index.first,
    1974             :                  " needs to be provided when setting all molalities");
    1975             :   }
    1976          22 :   for (unsigned sp = 0; sp < _num_surface_pot; ++sp)
    1977             :   {
    1978             :     const unsigned ind =
    1979           8 :         std::distance(names.begin(),
    1980             :                       std::find(names.begin(),
    1981             :                                 names.end(),
    1982           8 :                                 _mgd.surface_sorption_name[sp] + "_surface_potential_expr"));
    1983           8 :     if (ind < num_names)
    1984             :     {
    1985           7 :       if (values[ind] <= 0.0)
    1986           1 :         mooseError("Surface-potential expression for mineral ",
    1987           1 :                    _mgd.surface_sorption_name[sp],
    1988             :                    " cannot be ",
    1989             :                    values[ind],
    1990             :                    ": it must be positive");
    1991           6 :       _surface_pot_expr[sp] = values[ind];
    1992             :     }
    1993             :     else
    1994           1 :       mooseError("Surface potential for mineral ",
    1995           1 :                  _mgd.surface_sorption_name[sp],
    1996             :                  " needs to be provided when setting all molalities");
    1997             :   }
    1998          16 :   for (const auto & name_index : _mgd.kin_species_index)
    1999             :   {
    2000             :     const unsigned ind =
    2001           4 :         std::distance(names.begin(), std::find(names.begin(), names.end(), name_index.first));
    2002           4 :     if (ind < num_names)
    2003           3 :       setKineticMoles(name_index.second, values[ind]);
    2004             :     else
    2005           1 :       mooseError("Moles for species ",
    2006             :                  name_index.first,
    2007             :                  " needs to be provided when setting all molalities");
    2008             :   }
    2009             : 
    2010             :   /*
    2011             :    * STEP 2: Alter _constraint_values if necessary
    2012             :    */
    2013             :   // If some of the constraints_from_molalities are false, then the molalities provided to this
    2014             :   // method may have to be modified to satisfy the contraints: this alters _basis_molality so
    2015             :   // must occur first
    2016         119 :   for (unsigned i = 0; i < _num_basis; ++i)
    2017             :   {
    2018         107 :     const ConstraintMeaningEnum meaning = _constraint_meaning[i];
    2019         107 :     if (meaning == ConstraintMeaningEnum::KG_SOLVENT_WATER ||
    2020         107 :         meaning == ConstraintMeaningEnum::FREE_MOLALITY ||
    2021             :         meaning == ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES)
    2022             :     {
    2023          25 :       if (constraints_from_molalities[i])
    2024             :       {
    2025             :         // molalities provided to this method dictate the contraints:
    2026          16 :         _constraint_value[i] = _basis_molality[i];
    2027          16 :         _original_constraint_value[i] = _constraint_value[i];
    2028             :       }
    2029             :       else
    2030             :         // contraints take precedence over the molalities provided to this method:
    2031           9 :         _basis_molality[i] = _constraint_value[i];
    2032             :     }
    2033             :   }
    2034             : 
    2035             :   // Potentially alter _constraint_value for the BULK contraints:
    2036         119 :   for (unsigned i = 0; i < _num_basis; ++i)
    2037             :   {
    2038         107 :     const ConstraintMeaningEnum meaning = _constraint_meaning[i];
    2039         107 :     if (meaning == ConstraintMeaningEnum::MOLES_BULK_WATER ||
    2040         107 :         meaning == ConstraintMeaningEnum::MOLES_BULK_SPECIES)
    2041          70 :       if (constraints_from_molalities[i])
    2042             :       {
    2043             :         // the constraint value should be overridden by the molality-computed bulk mole number
    2044          40 :         _constraint_value[i] = computeBulkFromMolalities(i);
    2045          40 :         _original_constraint_value[i] = _constraint_value[i];
    2046             :       }
    2047             :   }
    2048             : 
    2049             :   // Potentially alter _constraint_value for ACTIVITY contraints:
    2050         111 :   for (unsigned i = 0; i < _num_basis; ++i)
    2051         101 :     if (constraints_from_molalities[i])
    2052             :     {
    2053          56 :       const ConstraintMeaningEnum meaning = _constraint_meaning[i];
    2054          56 :       if (meaning == ConstraintMeaningEnum::FUGACITY)
    2055           1 :         mooseError("Gas fugacity cannot be determined from molality: constraints_from_molalities "
    2056             :                    "must be set false for all gases");
    2057          55 :       else if (meaning == ConstraintMeaningEnum::ACTIVITY)
    2058             :       {
    2059           5 :         if (i == 0)
    2060           1 :           mooseError("Water activity cannot be determined from molalities: "
    2061             :                      "constraints_from_molalities "
    2062             :                      "must be set to false for water if activity of water is fixed");
    2063             :         // the constraint value should be overidden by the molality provided to this method
    2064           4 :         _constraint_value[i] = _basis_activity_coef[i] * _basis_molality[i];
    2065           4 :         _original_constraint_value[i] = _constraint_value[i];
    2066             :       }
    2067             :     }
    2068             : 
    2069             :   /*
    2070             :    * STEP 3: Follow the initialize() and computeConsistentConfiguration() methods
    2071             :    */
    2072             :   // assume done already: buildTemperatureDependentQuantities
    2073          10 :   enforceChargeBalanceIfSimple(_constraint_value, _bulk_moles_old);
    2074             :   // assume done already: buildAlgebraicInfo
    2075             :   // should not be done, as basis_molality is set by this method instead: initBulkAndFree
    2076          10 :   buildKnownBasisActivities(_basis_activity_known, _basis_activity);
    2077             :   // should not be done, as these are set by this method: _eqm_molality.assign
    2078             :   // should not be done, as these are set by this method: _surface_pot_expr.assign
    2079          10 :   _gac.setInternalParameters(_temperature, _mgd, _basis_molality, _eqm_molality, _kin_moles);
    2080          10 :   _gac.buildActivityCoefficients(_mgd, _basis_activity_coef, _eqm_activity_coef);
    2081             :   // for constraints_from_molalities = false then the following: (1) overrides the basis
    2082             :   // molality provided to this method; (2) produces a slightly inconsistent equilibrium
    2083             :   // geochemical system because basis_activity_coef was computed on the basis of the basis
    2084             :   // molalities provided to this method
    2085          10 :   updateBasisMolalityForKnownActivity(_basis_molality);
    2086          10 :   computeRemainingBasisActivities(_basis_activity);
    2087             :   // should not be done, as these are set by this method: computeEqmMolalities
    2088          10 :   computeBulk(_bulk_moles_old);
    2089             :   // should not be done, as these are set by this method: computeFreeMineralMoles
    2090          10 :   computeSorbingSurfaceArea(_sorbing_surface_area);
    2091          10 : }
    2092             : 
    2093             : const std::vector<GeochemicalSystem::ConstraintMeaningEnum> &
    2094         745 : GeochemicalSystem::getConstraintMeaning() const
    2095             : {
    2096         745 :   return _constraint_meaning;
    2097             : }
    2098             : 
    2099             : void
    2100         711 : GeochemicalSystem::closeSystem()
    2101             : {
    2102        3360 :   for (unsigned basis_ind = 0; basis_ind < _num_basis; ++basis_ind)
    2103        2649 :     if (_constraint_meaning[basis_ind] == ConstraintMeaningEnum::KG_SOLVENT_WATER ||
    2104        5068 :         _constraint_meaning[basis_ind] == ConstraintMeaningEnum::FREE_MOLALITY ||
    2105             :         _constraint_meaning[basis_ind] == ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES)
    2106         306 :       changeConstraintToBulk(basis_ind);
    2107         711 : }
    2108             : 
    2109             : void
    2110         412 : GeochemicalSystem::changeConstraintToBulk(unsigned basis_ind)
    2111             : {
    2112         412 :   if (basis_ind >= _num_basis)
    2113           1 :     mooseError("Cannot changeConstraintToBulk for species ",
    2114             :                basis_ind,
    2115             :                " because there are only ",
    2116           1 :                _num_basis,
    2117             :                " basis species");
    2118         411 :   if (_mgd.basis_species_gas[basis_ind])
    2119             :   {
    2120             :     // this is a special case where we have to swap out the gas in favour of an equilibrium
    2121             :     // species
    2122             :     unsigned swap_into_basis = 0;
    2123             :     bool legitimate_swap_found = false;
    2124             :     Real best_stoi = 0.0;
    2125         392 :     for (unsigned j = 0; j < _num_eqm; ++j)
    2126             :     {
    2127         386 :       if (_mgd.eqm_species_gas[j] || _mgd.eqm_stoichiometry(j, basis_ind) == 0.0 ||
    2128             :           _mgd.surface_sorption_related[j])
    2129         198 :         continue;
    2130             :       const Real stoi = std::abs(_mgd.eqm_stoichiometry(j, basis_ind));
    2131         188 :       if (stoi > best_stoi)
    2132             :       {
    2133             :         best_stoi = stoi;
    2134             :         swap_into_basis = j;
    2135             :         legitimate_swap_found = true;
    2136             :       }
    2137             :     }
    2138           6 :     if (legitimate_swap_found)
    2139           6 :       performSwapNoCheck(basis_ind, swap_into_basis);
    2140             :     else
    2141           0 :       mooseError("Attempting to change constraint of gas ",
    2142             :                  _mgd.basis_species_name[basis_ind],
    2143             :                  " to MOLES_BULK_SPECIES, which involves a search for a suitable non-gas species "
    2144             :                  "to swap with.  No such species was found");
    2145             :   }
    2146             :   else
    2147         405 :     changeConstraintToBulk(basis_ind, computeBulkFromMolalities(basis_ind));
    2148         411 : }
    2149             : 
    2150             : void
    2151         408 : GeochemicalSystem::changeConstraintToBulk(unsigned basis_ind, Real value)
    2152             : {
    2153         408 :   if (basis_ind >= _num_basis)
    2154           1 :     mooseError("Cannot changeConstraintToBulk for species ",
    2155             :                basis_ind,
    2156             :                " because there are only ",
    2157           1 :                _num_basis,
    2158             :                " basis species");
    2159         407 :   if (_mgd.basis_species_gas[basis_ind])
    2160           1 :     mooseError("Attempting to changeConstraintToBulk for a gas species.  Since a swap is involved, "
    2161             :                "you cannot specify a value for the bulk number of moles.  You must use "
    2162             :                "changeConstraintToBulk(basis_ind) method instead of "
    2163             :                "changeConstraintToBulk(basis_ind, value)");
    2164         406 :   if (basis_ind == 0)
    2165         135 :     _constraint_meaning[basis_ind] = ConstraintMeaningEnum::MOLES_BULK_WATER;
    2166             :   else
    2167         271 :     _constraint_meaning[basis_ind] = ConstraintMeaningEnum::MOLES_BULK_SPECIES;
    2168         406 :   setConstraintValue(basis_ind, value);
    2169             : 
    2170             :   // it is possible that FIXED_ACTIVITY just became MOLES_BULK_SPECIES
    2171         406 :   buildKnownBasisActivities(_basis_activity_known, _basis_activity);
    2172             : 
    2173             :   // it is likely the algebraic system has changed
    2174         406 :   buildAlgebraicInfo(_in_algebraic_system,
    2175         406 :                      _num_basis_in_algebraic_system,
    2176         406 :                      _num_in_algebraic_system,
    2177         406 :                      _algebraic_index,
    2178         406 :                      _basis_index);
    2179         406 : }
    2180             : 
    2181             : void
    2182       57419 : GeochemicalSystem::addToBulkMoles(unsigned basis_ind, Real value)
    2183             : {
    2184       57419 :   if (basis_ind >= _num_basis)
    2185           1 :     mooseError("Cannot addToBulkMoles for species ",
    2186             :                basis_ind,
    2187             :                " because there are only ",
    2188           1 :                _num_basis,
    2189             :                " basis species");
    2190       57418 :   if (!(_constraint_meaning[basis_ind] ==
    2191             :             GeochemicalSystem::ConstraintMeaningEnum::MOLES_BULK_WATER ||
    2192             :         _constraint_meaning[basis_ind] ==
    2193             :             GeochemicalSystem::ConstraintMeaningEnum::MOLES_BULK_SPECIES))
    2194             :     return;
    2195       51976 :   setConstraintValue(basis_ind, _constraint_value[basis_ind] + value);
    2196             : }
    2197             : 
    2198             : void
    2199       52924 : GeochemicalSystem::setConstraintValue(unsigned basis_ind, Real value)
    2200             : {
    2201       52924 :   if (basis_ind >= _num_basis)
    2202           1 :     mooseError("Cannot setConstraintValue for species ",
    2203             :                basis_ind,
    2204             :                " because there are only ",
    2205           1 :                _num_basis,
    2206             :                " basis species");
    2207       52923 :   _constraint_value[basis_ind] = value;
    2208       52923 :   _original_constraint_value[basis_ind] = value;
    2209       52923 :   switch (_constraint_meaning[basis_ind])
    2210             :   {
    2211       52386 :     case ConstraintMeaningEnum::MOLES_BULK_SPECIES:
    2212             :     case ConstraintMeaningEnum::MOLES_BULK_WATER:
    2213             :     {
    2214       52386 :       alterSystemBecauseBulkChanged();
    2215       52386 :       break;
    2216             :     }
    2217           1 :     case ConstraintMeaningEnum::KG_SOLVENT_WATER:
    2218             :     case ConstraintMeaningEnum::FREE_MOLALITY:
    2219             :     case ConstraintMeaningEnum::FREE_MOLES_MINERAL_SPECIES:
    2220             :     {
    2221             :       // the changes resulting from this change are very similar to setAlgebraicVariables
    2222           1 :       _basis_molality[basis_ind] = value;
    2223           1 :       computeConsistentConfiguration();
    2224           1 :       break;
    2225             :     }
    2226          50 :     case ConstraintMeaningEnum::FUGACITY:
    2227             :     {
    2228             :       // the changes resulting from this change are very similar to setAlgebraicVariables
    2229          50 :       _basis_activity[basis_ind] = value;
    2230          50 :       _basis_molality[basis_ind] = 0.0;
    2231          50 :       computeConsistentConfiguration();
    2232          50 :       break;
    2233             :     }
    2234         486 :     case ConstraintMeaningEnum::ACTIVITY:
    2235             :     {
    2236             :       // the changes resulting from this change are very similar to setAlgebraicVariables
    2237         486 :       _basis_activity[basis_ind] = value;
    2238         486 :       _basis_molality[basis_ind] = _basis_activity[basis_ind] / _basis_activity_coef[basis_ind];
    2239         486 :       computeConsistentConfiguration();
    2240         486 :       break;
    2241             :     }
    2242             :   }
    2243       52923 : }
    2244             : 
    2245             : void
    2246       52386 : GeochemicalSystem::alterSystemBecauseBulkChanged()
    2247             : {
    2248             :   // Altering the constraints on bulk number of moles impacts various other things.
    2249             :   // Here, we follow the initialize() and computeConsistentConfiguration() methods, picking out
    2250             :   // what might have changed.
    2251             :   // Because the constraint meanings have changed, enforcing charge-neutrality might be easy
    2252       52386 :   enforceChargeBalanceIfSimple(_constraint_value, _bulk_moles_old);
    2253             :   // Because the constraint values have changed, either through charge-neutrality or directly
    2254             :   // through changing the constraint values, the bulk moles must be updated:
    2255      475229 :   for (unsigned i = 0; i < _num_basis; ++i)
    2256      422843 :     if (_constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_SPECIES ||
    2257             :         _constraint_meaning[i] == ConstraintMeaningEnum::MOLES_BULK_WATER)
    2258      394573 :       _bulk_moles_old[i] = _constraint_value[i];
    2259             :   // Because the bulk mineral moles might have changed, the free mineral moles might have
    2260             :   // changed
    2261       52386 :   computeFreeMineralMoles(_basis_molality); // free mineral moles might be changed
    2262       52386 : }
    2263             : 
    2264             : const std::string &
    2265          51 : GeochemicalSystem::getOriginalRedoxLHS() const
    2266             : {
    2267          51 :   return _original_redox_lhs;
    2268             : }
    2269             : 
    2270             : void
    2271          62 : GeochemicalSystem::setModelGeochemicalDatabase(const ModelGeochemicalDatabase & mgd)
    2272             : {
    2273          62 :   _mgd = mgd;
    2274          62 : }
    2275             : 
    2276             : const GeochemistrySpeciesSwapper &
    2277          71 : GeochemicalSystem::getSwapper() const
    2278             : {
    2279          71 :   return _swapper;
    2280             : }
    2281             : 
    2282             : void
    2283         107 : GeochemicalSystem::setMineralRelatedFreeMoles(Real value)
    2284             : {
    2285             :   // mole numbers of basis minerals
    2286         916 :   for (unsigned i = 0; i < _num_basis; ++i)
    2287         809 :     if (_mgd.basis_species_mineral[i])
    2288          77 :       _basis_molality[i] = value;
    2289             : 
    2290             :   // mole numbers of sorption sites
    2291         107 :   for (const auto & name_info :
    2292         109 :        _mgd.surface_complexation_info) // all minerals involved in surface complexation
    2293           2 :     for (const auto & name_frac :
    2294           6 :          name_info.second.sorption_sites) // all sorption sites on the given mineral
    2295             :     {
    2296           4 :       if (_mgd.basis_species_index.count(name_frac.first))
    2297           4 :         _basis_molality[_mgd.basis_species_index.at(name_frac.first)] = value;
    2298             :       else
    2299           0 :         _eqm_molality[_mgd.eqm_species_index.at(name_frac.first)] = value;
    2300             :     }
    2301             : 
    2302             :   // mole numbers of sorbed equilibrium species
    2303        2749 :   for (unsigned j = 0; j < _num_eqm; ++j)
    2304             :   {
    2305        2642 :     if (_mgd.eqm_species_mineral[j])
    2306         361 :       _eqm_molality[j] = 0.0; // Note: explicitly setting to zero, irrespective of user input,
    2307             :                               // to ensure consistency with the rest of the code
    2308        2642 :     if (_mgd.surface_sorption_related[j])
    2309           3 :       _eqm_molality[j] = value;
    2310             :   }
    2311             : 
    2312             :   // mole numbers of kinetic species
    2313         110 :   for (unsigned k = 0; k < _num_kin; ++k)
    2314           3 :     if (_mgd.kin_species_mineral[k])
    2315           3 :       _kin_moles[k] = value;
    2316         107 : }
    2317             : 
    2318             : Real
    2319      132029 : GeochemicalSystem::getEquilibriumActivity(unsigned eqm_ind) const
    2320             : {
    2321      132029 :   if (eqm_ind >= _num_eqm)
    2322           1 :     mooseError("Cannot retrieve activity for equilibrium species ",
    2323             :                eqm_ind,
    2324             :                " since there are only ",
    2325           1 :                _num_eqm,
    2326             :                " equilibrium species");
    2327      132028 :   if (_mgd.eqm_species_mineral[eqm_ind])
    2328             :     return 1.0;
    2329      126776 :   else if (_mgd.eqm_species_gas[eqm_ind])
    2330             :   {
    2331             :     Real log10f = 0.0;
    2332        2536 :     for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
    2333        2232 :       log10f += _mgd.eqm_stoichiometry(eqm_ind, basis_i) * std::log10(_basis_activity[basis_i]);
    2334         304 :     log10f -= getLog10K(eqm_ind);
    2335         304 :     return std::pow(10.0, log10f);
    2336             :   }
    2337             :   else
    2338      126472 :     return _eqm_activity_coef[eqm_ind] * _eqm_molality[eqm_ind];
    2339             : }
    2340             : 
    2341             : const std::vector<Real> &
    2342           1 : GeochemicalSystem::computeAndGetEquilibriumActivity()
    2343             : {
    2344           7 :   for (unsigned j = 0; j < _num_eqm; ++j)
    2345           6 :     _eqm_activity[j] = getEquilibriumActivity(j);
    2346           1 :   return _eqm_activity;
    2347             : }
    2348             : 
    2349             : void
    2350        4570 : GeochemicalSystem::updateOldWithCurrent(const DenseVector<Real> & mole_additions)
    2351             : {
    2352        4570 :   if (mole_additions.size() != _num_basis + _num_kin)
    2353           1 :     mooseError("The increment in mole numbers (mole_additions) needs to be of size ",
    2354           1 :                _num_basis,
    2355             :                " + ",
    2356           1 :                _num_kin,
    2357             :                " but it is of size ",
    2358           1 :                mole_additions.size());
    2359             : 
    2360             :   // Copy the current kin_moles to the old values
    2361        5540 :   for (unsigned k = 0; k < _num_kin; ++k)
    2362         971 :     _kin_moles_old[k] = _kin_moles[k];
    2363             : 
    2364             :   // The following:
    2365             :   // - just returns if constraint is not BULK type.
    2366             :   // - ensures charge balance if simple
    2367             :   // - sets the constraint value and bulk_moles_old (for BULK-type species)
    2368             :   // - computes basis_molality for the BULK-type mineral species
    2369       29504 :   for (unsigned i = 0; i < _num_basis; ++i)
    2370       24935 :     addToBulkMoles(i, mole_additions(i));
    2371             : 
    2372             :   // The following:
    2373             :   // - sets bulk_moles_old to the Constraint value for BULK-type species (duplicating the above
    2374             :   // function)
    2375             :   // - for the other species, computes bulk_moles_old from the molalities
    2376        4569 :   computeBulk(_bulk_moles_old);
    2377             : 
    2378             :   // It is possible that the user would also like to enforceChargeBalance() now, and that is fine
    2379             :   // - there will be no negative consequences
    2380        4569 : }
    2381             : 
    2382             : void
    2383       40045 : GeochemicalSystem::addKineticRates(Real dt,
    2384             :                                    DenseVector<Real> & mole_additions,
    2385             :                                    DenseMatrix<Real> & dmole_additions)
    2386             : {
    2387       40045 :   if (_num_kin == 0)
    2388       33750 :     return;
    2389             : 
    2390             :   // check sizes
    2391        6295 :   const unsigned tot = mole_additions.size();
    2392        6295 :   if (!(tot == _num_kin + _num_basis && dmole_additions.m() == tot && dmole_additions.n() == tot))
    2393           3 :     mooseError("addKineticRates: incorrectly sized additions: ",
    2394             :                tot,
    2395             :                " ",
    2396           3 :                dmole_additions.m(),
    2397             :                " ",
    2398           3 :                dmole_additions.n());
    2399             : 
    2400             :   // construct eqm_activity for species that we need
    2401      187143 :   for (unsigned j = 0; j < _num_eqm; ++j)
    2402      539843 :     if (_mgd.eqm_species_gas[j] || _mgd.eqm_species_name[j] == "H+" ||
    2403      178141 :         _mgd.eqm_species_name[j] == "OH-")
    2404        8460 :       _eqm_activity[j] = getEquilibriumActivity(j);
    2405             : 
    2406             :   // calculate the rates and put into appropriate slots
    2407             :   Real rate;
    2408             :   Real drate_dkin;
    2409        6292 :   std::vector<Real> drate_dmol(_num_basis);
    2410       12200 :   for (const auto & krd : _mgd.kin_rate)
    2411             :   {
    2412        5908 :     const unsigned kin = krd.kinetic_species_index;
    2413       11816 :     GeochemistryKineticRateCalculator::calculateRate(krd.promoting_indices,
    2414        5908 :                                                      krd.promoting_monod_indices,
    2415        5908 :                                                      krd.promoting_half_saturation,
    2416        5908 :                                                      krd.description,
    2417        5908 :                                                      _mgd.basis_species_name,
    2418        5908 :                                                      _mgd.basis_species_gas,
    2419        5908 :                                                      _basis_molality,
    2420        5908 :                                                      _basis_activity,
    2421        5908 :                                                      _basis_activity_known,
    2422        5908 :                                                      _mgd.eqm_species_name,
    2423        5908 :                                                      _mgd.eqm_species_gas,
    2424        5908 :                                                      _eqm_molality,
    2425        5908 :                                                      _eqm_activity,
    2426        5908 :                                                      _mgd.eqm_stoichiometry,
    2427             :                                                      _kin_moles[kin],
    2428        5908 :                                                      _mgd.kin_species_molecular_weight[kin],
    2429        5908 :                                                      _kin_log10K[kin],
    2430             :                                                      log10KineticActivityProduct(kin),
    2431        5908 :                                                      _mgd.kin_stoichiometry,
    2432             :                                                      kin,
    2433             :                                                      _temperature,
    2434             :                                                      rate,
    2435             :                                                      drate_dkin,
    2436             :                                                      drate_dmol);
    2437             : 
    2438             :     /* the following block of code may be confusing at first sight.
    2439             :      * (1) The usual case is that the kinetic reaction is written
    2440             :      * kinetic -> basis_species, and direction = BOTH, and kinetic_bio_efficiency = -1
    2441             :      * In this case, the following does mole_additions(kinetic_species) += -1 * rate * dt
    2442             :      * If rate > 0 then the kinetic species decreases in mass.
    2443             :      * The solver will then detect that the kinetic_species has changed, and adjust basis_species
    2444             :      * accordingly, viz it will add rate * dt of basis species to the aqueous solution.
    2445             :      * (2) The common biologically-catalysed case is the reaction is written
    2446             :      * reactants -> products, catalysed by microbe.  In the database file this is written as
    2447             :      * microbe -> -reactants + products
    2448             :      * The input file will set direction = BOTH, and kinetic_bio_efficiency != -1
    2449             :      * Suppose that rate > 0, corresponding to reactants being converted to products.
    2450             :      * With kinetic_bio_efficiency = -1) this would correspond to microbe decreasing in mass,
    2451             :      * however with kinetic_bio_efficiency > 0, this is not true.
    2452             :      * The following block of code sets
    2453             :      * mole_additions(microbe) = kinetic_bio_efficiency * rate * dt > 0
    2454             :      * that is, the microbe mass is increasing.
    2455             :      * However, this means the solver will decrease products and increase reactants,
    2456             :      * by kinetic_bio_efficiency * rate * dt because it sees the reaction microbe -> -reactants +
    2457             :      * products in the database file.
    2458             :      * This is clearly wrong, because we actually want the end result to be that the products have
    2459             :      * increased by rate * dt, and the reactants to have decreased by rate * dt.
    2460             :      * So, to counter the solver, we add (-1 - kinetic_bio_efficiency) * rate * dt of reactants and
    2461             :      * add (1 + kinetic_bio_efficienty) * rate * dt of products.
    2462             :      * (3) The other situation is the biological death, where direction = DEATH.
    2463             :      * In this case the database file contains microbe -> -reactants + products
    2464             :      * However, independent of rate, we don't want to change the molality of reactants or products.
    2465             :      * Following the logic of (2), to counter the solver, we add -kinetic_bio_efficiency * rate * dt
    2466             :      * of reactants and kinetic_bio_efficiency * rate * dt of products
    2467             :      */
    2468        5908 :     const unsigned ind = kin + _num_basis;
    2469        5908 :     mole_additions(ind) += krd.description.kinetic_bio_efficiency * rate * dt;
    2470        5908 :     dmole_additions(ind, ind) += krd.description.kinetic_bio_efficiency * drate_dkin * dt;
    2471        5908 :     const Real extra_additions = (krd.description.direction == DirectionChoiceEnum::DEATH)
    2472        5908 :                                      ? krd.description.kinetic_bio_efficiency
    2473        5675 :                                      : krd.description.kinetic_bio_efficiency + 1.0;
    2474       52151 :     for (unsigned i = 0; i < _num_basis; ++i)
    2475             :     {
    2476       46243 :       dmole_additions(ind, i) += krd.description.kinetic_bio_efficiency * drate_dmol[i] * dt;
    2477       46243 :       const Real stoi_fac = _mgd.kin_stoichiometry(kin, i) * extra_additions * dt;
    2478       46243 :       mole_additions(i) += stoi_fac * rate;
    2479       46243 :       dmole_additions(i, ind) += stoi_fac * drate_dkin;
    2480      451184 :       for (unsigned j = 0; j < _num_basis; ++j)
    2481      404941 :         dmole_additions(i, j) += stoi_fac * drate_dmol[j];
    2482             :     }
    2483             : 
    2484        5908 :     const Real eff = krd.description.progeny_efficiency;
    2485        5908 :     if (eff != 0.0)
    2486             :     {
    2487         166 :       const unsigned bio_i = krd.progeny_index;
    2488         166 :       if (bio_i < _num_basis)
    2489             :       {
    2490         165 :         mole_additions(bio_i) += eff * rate * dt;
    2491         165 :         dmole_additions(bio_i, ind) += eff * drate_dkin * dt;
    2492        1805 :         for (unsigned i = 0; i < _num_basis; ++i)
    2493        1640 :           dmole_additions(bio_i, i) += eff * drate_dmol[i] * dt;
    2494             :       }
    2495             :       else
    2496             :       {
    2497           6 :         for (unsigned i = 0; i < _num_basis; ++i)
    2498             :         {
    2499           5 :           mole_additions(i) += _mgd.eqm_stoichiometry(bio_i - _num_basis, i) * eff * rate * dt;
    2500           5 :           dmole_additions(i, ind) +=
    2501           5 :               _mgd.eqm_stoichiometry(bio_i - _num_basis, i) * eff * drate_dkin * dt;
    2502          30 :           for (unsigned j = 0; j < _num_basis; ++j)
    2503          25 :             dmole_additions(i, j) +=
    2504          25 :                 _mgd.eqm_stoichiometry(bio_i - _num_basis, i) * eff * drate_dmol[j] * dt;
    2505             :         }
    2506             :       }
    2507             :     }
    2508             :   }
    2509             : }

Generated by: LCOV version 1.14