LCOV - code coverage report
Current view: top level - src/utils - PertinentGeochemicalSystem.C (source / functions) Hit Total Coverage
Test: idaholab/moose geochemistry: 602416 Lines: 469 475 98.7 %
Date: 2025-07-18 11:37:48 Functions: 20 20 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 "PertinentGeochemicalSystem.h"
      11             : 
      12        1178 : PertinentGeochemicalSystem::PertinentGeochemicalSystem(
      13             :     const GeochemicalDatabaseReader & db,
      14             :     const std::vector<std::string> & basis_species,
      15             :     const std::vector<std::string> & minerals,
      16             :     const std::vector<std::string> & gases,
      17             :     const std::vector<std::string> & kinetic_minerals,
      18             :     const std::vector<std::string> & kinetic_redox,
      19             :     const std::vector<std::string> & kinetic_surface_species,
      20             :     const std::string & redox_ox,
      21        1178 :     const std::string & redox_e)
      22        1178 :   : _db(db),
      23        1178 :     _basis_index(),
      24        1178 :     _basis_info(),
      25        1178 :     _mineral_index(),
      26        1178 :     _mineral_info(),
      27        1178 :     _gas_index(),
      28        1178 :     _gas_info(),
      29        1178 :     _kinetic_mineral_index(),
      30        1178 :     _kinetic_mineral_info(),
      31        1178 :     _kinetic_redox_index(),
      32        1178 :     _kinetic_redox_info(),
      33        1178 :     _kinetic_surface_index(),
      34        1178 :     _kinetic_surface_info(),
      35        1178 :     _secondary_index(),
      36        1178 :     _secondary_info(),
      37        1178 :     _redox_ox(redox_ox),
      38        1178 :     _redox_e(redox_e),
      39        1178 :     _model(_db)
      40             : {
      41             :   // Use the constructor info to build the "index" and "info" structures
      42        1178 :   buildBasis(basis_species);
      43        1175 :   buildMinerals(minerals);
      44        1173 :   buildGases(gases);
      45        1171 :   buildKineticMinerals(kinetic_minerals);
      46        1168 :   buildKineticRedox(kinetic_redox);
      47        1165 :   buildKineticSurface(kinetic_surface_species);
      48             : 
      49             :   // Pull out all secondary equilibrium species: these are all relevant "redox couples" and
      50             :   // "secondary species" and "surface species"
      51        1163 :   buildSecondarySpecies();
      52             : 
      53             :   // Build minerals in the case that minerals = {"*"}
      54        1163 :   buildAllMinerals(minerals);
      55             : 
      56             :   // Check that everything can be expressed in terms of the basis, possibly via redox and secondary
      57             :   // species
      58        1163 :   checkMinerals(_mineral_info);
      59        1160 :   checkGases();
      60        1159 :   checkMinerals(_kinetic_mineral_info);
      61        1157 :   checkKineticRedox();
      62        1156 :   checkKineticSurfaceSpecies();
      63             : 
      64             :   // Populate _model
      65        1155 :   createModel();
      66        1378 : }
      67             : 
      68             : unsigned
      69         196 : PertinentGeochemicalSystem::getIndexOfOriginalBasisSpecies(const std::string & name) const
      70             : {
      71             :   try
      72             :   {
      73         195 :     return _basis_index.at(name);
      74             :   }
      75           1 :   catch (const std::out_of_range &)
      76             :   {
      77           1 :     mooseError("species ", name, " is not in the original basis");
      78           1 :   }
      79           0 :   catch (...)
      80             :   {
      81           0 :     throw;
      82           0 :   }
      83             : }
      84             : 
      85             : std::vector<std::string>
      86         318 : PertinentGeochemicalSystem::originalBasisNames() const
      87             : {
      88         318 :   std::vector<std::string> names(_basis_info.size());
      89        2091 :   for (const auto & name_ind : _basis_index)
      90        1773 :     names[name_ind.second] = name_ind.first;
      91         318 :   return names;
      92           0 : }
      93             : 
      94             : void
      95        1178 : PertinentGeochemicalSystem::buildBasis(const std::vector<std::string> & basis_species)
      96             : {
      97        1178 :   unsigned ind = 0;
      98        8148 :   for (const auto & name : basis_species)
      99             :   {
     100        8151 :     if (ind == 0 and name != "H2O")
     101           1 :       mooseError("First member of basis species list must be H2O");
     102             :     if (_basis_index.count(name) == 1)
     103           1 :       mooseError(name, " exists more than once in the basis species list");
     104             :     _basis_index.emplace(name, ind);
     105        6971 :     ind += 1;
     106        6971 :     if (_db.isBasisSpecies(name))
     107       18828 :       _basis_info.push_back(_db.getBasisSpecies({name})[name]);
     108         695 :     else if (_db.isRedoxSpecies(name))
     109             :     {
     110        2082 :       const GeochemistryRedoxSpecies rs = _db.getRedoxSpecies({name})[name];
     111             :       GeochemistryBasisSpecies bs;
     112             :       bs.name = rs.name;
     113         694 :       bs.radius = rs.radius;
     114         694 :       bs.charge = rs.charge;
     115         694 :       bs.molecular_weight = rs.molecular_weight;
     116         694 :       _basis_info.push_back(bs);
     117         694 :     }
     118             :     else
     119           1 :       mooseError(name + " does not exist in the basis species or redox species in " +
     120           1 :                  _db.filename());
     121             :   }
     122        8145 : }
     123             : 
     124             : void
     125        1175 : PertinentGeochemicalSystem::buildMinerals(const std::vector<std::string> & minerals)
     126             : {
     127        1175 :   unsigned ind = 0;
     128        1591 :   if (minerals.size() == 1 && minerals[0] == "*")
     129           1 :     return; // buildAllMinerals is called later
     130        2817 :   for (const auto & name : minerals)
     131             :   {
     132             :     if (_mineral_index.count(name) == 1)
     133           1 :       mooseError(name + " exists more than once in the minerals list");
     134             :     _mineral_index.emplace(name, ind);
     135        1644 :     ind += 1;
     136        4932 :     _mineral_info.push_back(_db.getMineralSpecies({name})[name]);
     137             :   }
     138        1644 : }
     139             : 
     140             : void
     141        1173 : PertinentGeochemicalSystem::buildGases(const std::vector<std::string> & gases)
     142             : {
     143        1173 :   unsigned ind = 0;
     144        1514 :   for (const auto & name : gases)
     145             :   {
     146             :     if (_gas_index.count(name) == 1)
     147           1 :       mooseError(name + " exists more than once in the gases list");
     148             :     _gas_index.emplace(name, ind);
     149         342 :     ind += 1;
     150        1026 :     const GeochemistryGasSpecies gas = _db.getGasSpecies({name})[name];
     151         341 :     _gas_info.push_back(gas);
     152         341 :   }
     153        1513 : }
     154             : 
     155             : void
     156        1171 : PertinentGeochemicalSystem::buildKineticMinerals(const std::vector<std::string> & kinetic_minerals)
     157             : {
     158        1171 :   unsigned ind = 0;
     159        1423 :   for (const auto & name : kinetic_minerals)
     160             :   {
     161             :     if (_kinetic_mineral_index.count(name) == 1)
     162           1 :       mooseError(name + " exists more than once in the kinetic_minerals list");
     163             :     if (_mineral_index.count(name) == 1)
     164           1 :       mooseError(name + " exists in both the minerals and kinetic_minerals lists");
     165             :     _kinetic_mineral_index.emplace(name, ind);
     166         253 :     ind += 1;
     167         759 :     _kinetic_mineral_info.push_back(_db.getMineralSpecies({name})[name]);
     168             :   }
     169        1421 : }
     170             : 
     171             : void
     172        1168 : PertinentGeochemicalSystem::buildKineticRedox(const std::vector<std::string> & kinetic_redox)
     173             : {
     174        1168 :   unsigned ind = 0;
     175        1218 :   for (const auto & name : kinetic_redox)
     176             :   {
     177             :     if (_kinetic_redox_index.count(name) == 1)
     178           1 :       mooseError(name + " exists more than once in the kinetic_redox list");
     179             :     if (_basis_index.count(name) == 1)
     180           1 :       mooseError(name + " exists in both the basis_species and kinetic_redox lists");
     181             :     _kinetic_redox_index.emplace(name, ind);
     182          51 :     ind += 1;
     183         153 :     _kinetic_redox_info.push_back(_db.getRedoxSpecies({name})[name]);
     184             :   }
     185        1216 : }
     186             : 
     187             : void
     188        1165 : PertinentGeochemicalSystem::buildKineticSurface(
     189             :     const std::vector<std::string> & kinetic_surface_species)
     190             : {
     191        1165 :   unsigned ind = 0;
     192        1188 :   for (const auto & name : kinetic_surface_species)
     193             :   {
     194             :     if (_kinetic_surface_index.count(name) == 1)
     195           1 :       mooseError(name + " exists more than once in the kinetic_surface_species list");
     196             :     _kinetic_surface_index.emplace(name, ind);
     197          24 :     ind += 1;
     198          72 :     _kinetic_surface_info.push_back(_db.getSurfaceSpecies({name})[name]);
     199             :   }
     200        1187 : }
     201             : 
     202             : void
     203        1163 : PertinentGeochemicalSystem::buildSecondarySpecies()
     204             : {
     205             :   // run through all redox couples, including them if:
     206             :   // - they are not part of the kinetic_redox list
     207             :   // - they are not part of the basis_species list
     208             :   // - their reaction involves only basis_species or secondary species already encountered
     209        1163 :   unsigned ind = 0;
     210       30362 :   for (const auto & name : _db.redoxCoupleNames())
     211             :   {
     212             :     if (_kinetic_redox_index.count(name) == 0 && _basis_index.count(name) == 0)
     213             :     {
     214       85401 :       const GeochemistryRedoxSpecies rs = _db.getRedoxSpecies({name})[name];
     215             :       // check all reaction species are in the basis
     216             :       bool all_species_in_basis_or_sec = true;
     217       67341 :       for (const auto & element : rs.basis_species)
     218       66041 :         if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     219             :         {
     220             :           all_species_in_basis_or_sec = false;
     221             :           break;
     222             :         }
     223       28467 :       if (all_species_in_basis_or_sec)
     224             :       {
     225             :         _secondary_index.emplace(name, ind);
     226        1300 :         ind += 1;
     227             :         GeochemistryEquilibriumSpecies ss;
     228             :         ss.name = rs.name;
     229             :         ss.basis_species = rs.basis_species;
     230        1300 :         ss.equilibrium_const = rs.equilibrium_const;
     231        1300 :         ss.radius = rs.radius;
     232        1300 :         ss.charge = rs.charge;
     233        1300 :         ss.molecular_weight = rs.molecular_weight;
     234        1300 :         _secondary_info.push_back(ss);
     235        1300 :       }
     236       28467 :     }
     237        1163 :   }
     238             : 
     239             :   // run through all secondary species, including them if:
     240             :   // - their reaction involves only basis_species, or secondary species already encountered
     241             :   // - the name is not _redox_e (which is usually "e-" the free electron)
     242      300486 :   for (const auto & name : _db.secondarySpeciesNames())
     243             :   {
     244      299323 :     if (name == _redox_e)
     245         989 :       continue;
     246      895002 :     const GeochemistryEquilibriumSpecies ss = _db.getEquilibriumSpecies({name})[name];
     247             :     // check all reaction species are in the basis
     248             :     bool all_species_in_basis_or_sec = true;
     249      516298 :     for (const auto & element : ss.basis_species)
     250      499334 :       if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     251             :       {
     252             :         all_species_in_basis_or_sec = false;
     253             :         break;
     254             :       }
     255      298334 :     if (all_species_in_basis_or_sec)
     256             :     {
     257             :       _secondary_index.emplace(name, ind);
     258       16964 :       ind += 1;
     259       16964 :       _secondary_info.push_back(ss);
     260             :     }
     261      299497 :   }
     262             : 
     263             :   // run through all surface species, including them if:
     264             :   // - their reaction involves only basis_species or secondary species encountered so far
     265             :   // - they are not in the kinetic_surface_species list
     266       12083 :   for (const auto & name : _db.surfaceSpeciesNames())
     267             :   {
     268             :     if (_kinetic_surface_index.count(name) == 0)
     269             :     {
     270       32703 :       const GeochemistrySurfaceSpecies ss = _db.getSurfaceSpecies({name})[name];
     271             :       // check all reaction species are in the basis
     272             :       bool all_species_in_basis_or_sec = true;
     273       19125 :       for (const auto & element : ss.basis_species)
     274       18687 :         if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     275             :         {
     276             :           all_species_in_basis_or_sec = false;
     277             :           break;
     278             :         }
     279       10901 :       if (all_species_in_basis_or_sec)
     280             :       {
     281             :         GeochemistryEquilibriumSpecies to_sec;
     282             :         to_sec.name = ss.name;
     283             :         to_sec.basis_species = ss.basis_species;
     284         438 :         to_sec.radius = -1.5; // flag to activity calculators that activity coefficient = 1
     285         438 :         to_sec.charge = ss.charge;
     286         438 :         to_sec.molecular_weight = ss.molecular_weight;
     287         438 :         const Real T0 = _db.getTemperatures()[0];
     288        3942 :         for (const auto & temp : _db.getTemperatures())
     289        3504 :           to_sec.equilibrium_const.push_back(ss.log10K + ss.dlog10KdT * (temp - T0));
     290             : 
     291             :         _secondary_index.emplace(name, ind);
     292         438 :         ind += 1;
     293         438 :         _secondary_info.push_back(to_sec);
     294         438 :       }
     295       10901 :     }
     296        1163 :   }
     297      338865 : }
     298             : 
     299             : void
     300        1163 : PertinentGeochemicalSystem::buildAllMinerals(const std::vector<std::string> & minerals)
     301             : {
     302        1575 :   if (!(minerals.size() == 1 && minerals[0] == "*"))
     303        1162 :     return; // buildMinerals has done its job of building _mineral_info and _mineral_index
     304           1 :   unsigned ind = 0;
     305           7 :   for (const auto & name_ms : _db.getMineralSpecies(_db.mineralSpeciesNames()))
     306             :   {
     307           6 :     if (_kinetic_mineral_index.count(name_ms.first) == 1)
     308           1 :       continue;
     309             :     bool known_basis_only = true;
     310          15 :     for (const auto & basis_stoi : name_ms.second.basis_species)
     311             :     {
     312          12 :       if (_basis_index.count(basis_stoi.first) == 0 &&
     313             :           _secondary_index.count(basis_stoi.first) == 0)
     314             :       {
     315             :         known_basis_only = false;
     316             :         break;
     317             :       }
     318             :     }
     319           5 :     if (known_basis_only)
     320             :     {
     321             :       _mineral_index.emplace(name_ms.first, ind);
     322           3 :       ind += 1;
     323           3 :       _mineral_info.push_back(name_ms.second);
     324             :     }
     325             :   }
     326             : }
     327             : 
     328             : bool
     329         335 : PertinentGeochemicalSystem::checkRedoxe()
     330             : {
     331             :   bool found = false;
     332       89496 :   for (const auto & name : _db.secondarySpeciesNames())
     333       89162 :     if (name == _redox_e)
     334             :     {
     335             :       found = true;
     336        1005 :       const GeochemistryEquilibriumSpecies ss = _db.getEquilibriumSpecies({name})[name];
     337        1339 :       for (const auto & element : ss.basis_species)
     338        1005 :         if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     339             :           return false;
     340         669 :     }
     341         334 :   return found;
     342         335 : }
     343             : 
     344             : void
     345         334 : PertinentGeochemicalSystem::buildRedoxeInfo(std::vector<Real> & redox_e_stoichiometry,
     346             :                                             std::vector<Real> & redox_e_log10K)
     347             : {
     348         334 :   const unsigned num_basis = _basis_info.size();
     349         334 :   const unsigned numT = _db.getTemperatures().size();
     350         334 :   redox_e_stoichiometry.assign(num_basis, 0.0);
     351         334 :   redox_e_log10K.assign(numT, 0.0);
     352       89490 :   for (const auto & name : _db.secondarySpeciesNames())
     353       89156 :     if (name == _redox_e)
     354             :     {
     355        1002 :       const GeochemistryEquilibriumSpecies ss = _db.getEquilibriumSpecies({name})[name];
     356        3006 :       for (unsigned i = 0; i < numT; ++i)
     357        2672 :         redox_e_log10K[i] = ss.equilibrium_const[i];
     358        1336 :       for (const auto & react : ss.basis_species)
     359             :       {
     360        1002 :         const Real stoi_coeff = react.second;
     361        1002 :         if (_model.basis_species_index.count(react.first) == 1)
     362             :         {
     363        1000 :           const unsigned col = _model.basis_species_index[react.first];
     364        1000 :           redox_e_stoichiometry[col] += react.second;
     365             :         }
     366             :         else if (_secondary_index.count(react.first) == 1)
     367             :         {
     368             :           // reaction species is not a basis component, but a secondary component.
     369             :           // So express stoichiometry in terms of the secondary component's reaction
     370           2 :           const unsigned sec_row = _model.eqm_species_index[react.first];
     371          18 :           for (unsigned i = 0; i < numT; ++i)
     372          16 :             redox_e_log10K[i] += stoi_coeff * _model.eqm_log10K(sec_row, i);
     373          12 :           for (unsigned col = 0; col < num_basis; ++col)
     374          10 :             redox_e_stoichiometry[col] += stoi_coeff * _model.eqm_stoichiometry(sec_row, col);
     375             :         }
     376             :       }
     377         668 :     }
     378         668 : }
     379             : 
     380             : void
     381        2322 : PertinentGeochemicalSystem::checkMinerals(
     382             :     const std::vector<GeochemistryMineralSpecies> & mineral_info) const
     383             : {
     384        4203 :   for (const auto & mineral : mineral_info)
     385             :   {
     386        8312 :     for (const auto & element : mineral.basis_species)
     387        6429 :       if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     388           9 :         mooseError("The reaction for " + mineral.name + " depends on " + element.first +
     389             :                    " which is not reducable to a set of basis species");
     390        2101 :     for (const auto & element : mineral.sorption_sites)
     391         220 :       if (_basis_index.count(element.first) == 0)
     392           6 :         mooseError("The sorbing sites for " + mineral.name + " include " + element.first +
     393             :                    " which is not in the basis_species list");
     394             :   }
     395        2317 : }
     396             : 
     397             : void
     398        1160 : PertinentGeochemicalSystem::checkGases() const
     399             : {
     400        1498 :   for (const auto & gas : _gas_info)
     401        1037 :     for (const auto & element : gas.basis_species)
     402         699 :       if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     403           3 :         mooseError("The reaction for " + gas.name + " depends on " + element.first +
     404             :                    " which is not reducable to a set of basis species");
     405        1159 : }
     406             : 
     407             : void
     408        1157 : PertinentGeochemicalSystem::checkKineticRedox() const
     409             : {
     410        1202 :   for (const auto & kr : _kinetic_redox_info)
     411         234 :     for (const auto & element : kr.basis_species)
     412         189 :       if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     413           3 :         mooseError("The reaction for " + kr.name + " depends on " + element.first +
     414             :                    " which is not reducable to a set of basis species");
     415        1156 : }
     416             : 
     417             : void
     418        1156 : PertinentGeochemicalSystem::checkKineticSurfaceSpecies() const
     419             : {
     420        1174 :   for (const auto & kr : _kinetic_surface_info)
     421          63 :     for (const auto & element : kr.basis_species)
     422          45 :       if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
     423           3 :         mooseError("The reaction for " + kr.name + " depends on " + element.first +
     424             :                    " which is not reducable to a set of basis species");
     425        1155 : }
     426             : 
     427             : void
     428        1155 : PertinentGeochemicalSystem::createModel()
     429             : {
     430        1155 :   const unsigned num_rows = _secondary_info.size() + _mineral_info.size() + _gas_info.size();
     431        1155 :   const unsigned num_cols = _basis_info.size();
     432        1155 :   const unsigned num_temperatures = _db.getTemperatures().size();
     433             :   unsigned ind = 0;
     434             : 
     435             :   // create basis_species_index map
     436             :   _model.basis_species_index = _basis_index;
     437             : 
     438             :   // extract the names
     439        1155 :   _model.basis_species_name = std::vector<std::string>(num_cols);
     440        8053 :   for (const auto & species : _basis_info)
     441        6898 :     _model.basis_species_name[_model.basis_species_index[species.name]] = species.name;
     442             : 
     443             :   // initially no basis species are minerals
     444        1155 :   _model.basis_species_mineral = std::vector<bool>(num_cols, false);
     445             : 
     446             :   // initially no basis species are gases
     447        1155 :   _model.basis_species_gas = std::vector<bool>(num_cols, false);
     448             : 
     449             :   // initially all basis species are involved in transport (this gets modified for surface species
     450             :   // below)
     451        1155 :   _model.basis_species_transported = std::vector<bool>(num_cols, true);
     452             : 
     453             :   // record the charge
     454        2310 :   _model.basis_species_charge = std::vector<Real>(num_cols, 0.0);
     455        8053 :   for (const auto & species : _basis_info)
     456        6898 :     _model.basis_species_charge[_model.basis_species_index[species.name]] = species.charge;
     457             : 
     458             :   // record the ionic radius
     459        2310 :   _model.basis_species_radius = std::vector<Real>(num_cols, 0.0);
     460        8053 :   for (const auto & species : _basis_info)
     461        6898 :     _model.basis_species_radius[_model.basis_species_index[species.name]] = species.radius;
     462             : 
     463             :   // record the molecular weight
     464        2310 :   _model.basis_species_molecular_weight = std::vector<Real>(num_cols, 0.0);
     465        8053 :   for (const auto & species : _basis_info)
     466        6898 :     _model.basis_species_molecular_weight[_model.basis_species_index[species.name]] =
     467        6898 :         species.molecular_weight;
     468             : 
     469             :   // record the molecular weight (zero for all species except minerals)
     470        1155 :   _model.basis_species_molecular_volume = std::vector<Real>(num_cols, 0.0);
     471             : 
     472             :   // the "eqm_species" stuff is rather long-winded because of the different data structures used
     473             :   // to hold secondary, mineral and gas info.  There is a bit of an overlap, however, so let's
     474             :   // create a new data structure that contains that overlapping info
     475             : 
     476        1155 :   std::vector<GeochemistryEquilibriumSpecies> overlap(_secondary_info);
     477        2789 :   for (const auto & species : _mineral_info)
     478             :   {
     479             :     GeochemistryEquilibriumSpecies es;
     480        1634 :     es.name = species.name;
     481        1634 :     es.molecular_weight = species.molecular_weight;
     482        1634 :     es.equilibrium_const = species.equilibrium_const;
     483             :     es.basis_species = species.basis_species;
     484        1634 :     overlap.push_back(es);
     485        1634 :   }
     486        1493 :   for (const auto & species : _gas_info)
     487             :   {
     488             :     GeochemistryEquilibriumSpecies es;
     489         338 :     es.name = species.name;
     490         338 :     es.molecular_weight = species.molecular_weight;
     491         338 :     es.equilibrium_const = species.equilibrium_const;
     492             :     es.basis_species = species.basis_species;
     493         338 :     overlap.push_back(es);
     494         338 :   }
     495             : 
     496             :   // create the eqm_species_index map
     497             :   ind = 0;
     498       21809 :   for (const auto & species : overlap)
     499       20654 :     _model.eqm_species_index[species.name] = ind++;
     500             : 
     501             :   // extract the names
     502        1155 :   _model.eqm_species_name = std::vector<std::string>(num_rows);
     503       21809 :   for (const auto & species : _model.eqm_species_index)
     504       20654 :     _model.eqm_species_name[species.second] = species.first;
     505             : 
     506             :   // create the eqm_species_mineral vector
     507        2310 :   _model.eqm_species_mineral = std::vector<bool>(num_rows, false);
     508        2789 :   for (const auto & species : _mineral_info)
     509        1634 :     _model.eqm_species_mineral[_model.eqm_species_index[species.name]] = true;
     510        1493 :   for (const auto & species : _gas_info)
     511         338 :     _model.eqm_species_mineral[_model.eqm_species_index[species.name]] = false;
     512             : 
     513             :   // create the eqm_species_gas vector
     514        2310 :   _model.eqm_species_gas = std::vector<bool>(num_rows, false);
     515        2789 :   for (const auto & species : _mineral_info)
     516        1634 :     _model.eqm_species_gas[_model.eqm_species_index[species.name]] = false;
     517        1493 :   for (const auto & species : _gas_info)
     518         338 :     _model.eqm_species_gas[_model.eqm_species_index[species.name]] = true;
     519             : 
     520             :   // create the eqm_species_transported vector (true for non-minerals) - gets modified below for
     521             :   // surface species
     522        2310 :   _model.eqm_species_transported = std::vector<bool>(num_rows, true);
     523        2789 :   for (const auto & species : _mineral_info)
     524        1634 :     _model.eqm_species_transported[_model.eqm_species_index.at(species.name)] = false;
     525             : 
     526             :   // record the charge
     527             :   _model.eqm_species_charge =
     528        2310 :       std::vector<Real>(num_rows, 0.0); // charge of gases and minerals is zero
     529       19837 :   for (const auto & species : _secondary_info)
     530       18682 :     _model.eqm_species_charge[_model.eqm_species_index[species.name]] = species.charge;
     531             : 
     532             :   // record the radius
     533             :   _model.eqm_species_radius =
     534        2310 :       std::vector<Real>(num_rows, 0.0); // ionic radius of gases and minerals is zero
     535       19837 :   for (const auto & species : _secondary_info)
     536       18682 :     _model.eqm_species_radius[_model.eqm_species_index[species.name]] = species.radius;
     537             : 
     538             :   // record the molecular weight
     539        1155 :   _model.eqm_species_molecular_weight = std::vector<Real>(num_rows, 0.0);
     540       21809 :   for (const auto & species : overlap)
     541       20654 :     _model.eqm_species_molecular_weight[_model.eqm_species_index[species.name]] =
     542       20654 :         species.molecular_weight;
     543             : 
     544             :   // record the molecular volume (zero for all species except minerals)
     545        2310 :   _model.eqm_species_molecular_volume = std::vector<Real>(num_rows, 0.0);
     546        2789 :   for (const auto & species : _mineral_info)
     547        1634 :     _model.eqm_species_molecular_volume[_model.eqm_species_index[species.name]] =
     548        1634 :         species.molecular_volume;
     549             : 
     550             :   // record surface-complexation info
     551        2789 :   for (const auto & species : _mineral_info)
     552        1634 :     if (species.surface_area != 0.0)
     553             :     {
     554             :       SurfaceComplexationInfo sci;
     555          60 :       sci.surface_area = species.surface_area;
     556             :       sci.sorption_sites = species.sorption_sites;
     557          60 :       _model.surface_complexation_info[species.name] = sci;
     558             :     }
     559        1401 :   for (const auto & species : _kinetic_mineral_info)
     560         246 :     if (species.surface_area != 0.0)
     561             :     {
     562             :       SurfaceComplexationInfo sci;
     563          48 :       sci.surface_area = species.surface_area;
     564             :       sci.sorption_sites = species.sorption_sites;
     565          48 :       _model.surface_complexation_info[species.name] = sci;
     566             :     }
     567             : 
     568             :   // record gas fugacity info
     569        1493 :   for (const auto & species : _gas_info)
     570         338 :     _model.gas_chi[species.name] = species.chi;
     571             : 
     572             :   // create the stoichiometry matrix
     573        1155 :   _model.eqm_stoichiometry.resize(num_rows, num_cols);
     574        1155 :   _model.eqm_log10K.resize(num_rows, num_temperatures);
     575             : 
     576             :   // populate the stoichiometry
     577       21809 :   for (const auto & species : overlap)
     578             :   {
     579       20654 :     const unsigned row = _model.eqm_species_index[species.name];
     580      185886 :     for (unsigned i = 0; i < num_temperatures; ++i)
     581      165232 :       _model.eqm_log10K(row, i) = species.equilibrium_const[i];
     582       75315 :     for (const auto & react : species.basis_species)
     583             :     {
     584       54661 :       const Real stoi_coeff = react.second;
     585       54661 :       if (_model.basis_species_index.count(react.first) == 1)
     586             :       {
     587       49956 :         const unsigned col = _model.basis_species_index[react.first];
     588       49956 :         _model.eqm_stoichiometry(row, col) += react.second;
     589             :       }
     590             :       else if (_secondary_index.count(react.first) == 1)
     591             :       {
     592             :         // reaction species is not a basis component, but a secondary component.
     593             :         // So express stoichiometry in terms of the secondary component's reaction
     594        4705 :         const unsigned sec_row = _model.eqm_species_index[react.first];
     595       42345 :         for (unsigned i = 0; i < num_temperatures; ++i)
     596       37640 :           _model.eqm_log10K(row, i) += stoi_coeff * _model.eqm_log10K(sec_row, i);
     597       65525 :         for (unsigned col = 0; col < num_cols; ++col)
     598       60820 :           _model.eqm_stoichiometry(row, col) += stoi_coeff * _model.eqm_stoichiometry(sec_row, col);
     599             :       }
     600             :       else
     601           0 :         mooseError("Species " + species.name + " includes " + react.first +
     602             :                    ", which cannot be expressed in terms of the basis.  Previous checks must be "
     603             :                    "erroneous!");
     604             :     }
     605             :   }
     606             : 
     607             :   // Build the redox information, if any.  Here we express any O2(aq) in the redox equations in
     608             :   // terms of redox_e (which is usually e-)
     609        1155 :   _model.redox_lhs = _redox_e;
     610        1155 :   std::vector<Real> redox_e_stoichiometry(num_cols, 0.0);
     611        1157 :   std::vector<Real> redox_e_log10K(num_temperatures, 0.0);
     612             :   std::vector<Real> redox_stoi;
     613             :   std::vector<Real> redox_log10K;
     614        1490 :   if ((_model.basis_species_index.count(_redox_ox) == 1) && checkRedoxe())
     615             :   {
     616             :     // construct the stoichiometry and log10K for _redox_e and put it
     617         334 :     buildRedoxeInfo(redox_e_stoichiometry, redox_e_log10K);
     618         334 :     redox_stoi.insert(redox_stoi.end(), redox_e_stoichiometry.begin(), redox_e_stoichiometry.end());
     619         334 :     redox_log10K.insert(redox_log10K.end(), redox_e_log10K.begin(), redox_e_log10K.end());
     620             :     // the electron reaction is
     621             :     // e- = nuw_i * basis_i + beta * O2(aq), where we've pulled out the O2(aq) because it's
     622             :     // special
     623         334 :     const unsigned o2_index = _model.basis_species_index.at(_redox_ox);
     624         334 :     const Real beta = redox_e_stoichiometry[o2_index];
     625         334 :     if (beta != 0.0)
     626             :     {
     627        3422 :       for (const auto & bs : _model.basis_species_index)
     628        3088 :         if (_db.isRedoxSpecies(bs.first))
     629             :         {
     630             :           // this basis species is a redox couple in disequilibrium
     631        1550 :           const GeochemistryRedoxSpecies rs = _db.getRedoxSpecies({bs.first})[bs.first];
     632             :           // check that its reaction involves only basis species, and record the stoichiometry in
     633             :           // the current basis
     634         516 :           std::vector<Real> stoi(num_cols, 0.0);
     635             :           bool only_involves_basis_species = true;
     636        1858 :           for (const auto & name_stoi : rs.basis_species)
     637             :           {
     638        1563 :             if (_model.basis_species_index.count(name_stoi.first) == 1)
     639        1342 :               stoi[_model.basis_species_index.at(name_stoi.first)] = name_stoi.second;
     640             :             else
     641             :             {
     642             :               only_involves_basis_species = false;
     643             :               break;
     644             :             }
     645             :           }
     646         516 :           if (!only_involves_basis_species)
     647         221 :             continue;
     648             :           // Reaction is now
     649             :           // redox = nu_i * basis_i + alpha * O2(aq), where we've pulled the O2(aq) out because
     650             :           // it's special. Now pull the redox couple to the RHS of the reaction, so we have 0 =
     651             :           // -redox + nu_i * basis_i + alpha * O2(aq)
     652         295 :           stoi[bs.second] = -1.0;
     653             :           // check that the stoichiometry involves O2(aq)
     654         295 :           const Real alpha = stoi[o2_index];
     655         295 :           if (alpha == 0.0)
     656           1 :             continue;
     657             :           // multiply equation -beta/alpha so it reads
     658             :           // 0 = -beta/alpha * (-redox + nu_i * basis_i) - beta * O2(aq)
     659        3280 :           for (unsigned basis_i = 0; basis_i < num_cols; ++basis_i)
     660        2986 :             stoi[basis_i] *= -beta / alpha;
     661             :           // add the equation to e- = nuw_i * basis_i + beta * O2(aq)
     662        3280 :           for (unsigned basis_i = 0; basis_i < num_cols; ++basis_i)
     663        2986 :             stoi[basis_i] += redox_e_stoichiometry[basis_i];
     664             :           // now the reation is e- = nuw_i * basis_i - beta/alpha * (-redox + nu_i * basis_i)
     665         294 :           redox_stoi.insert(redox_stoi.end(), stoi.begin(), stoi.end());
     666             : 
     667             :           // record the equilibrium constants
     668        2646 :           for (unsigned temp = 0; temp < num_temperatures; ++temp)
     669        2352 :             redox_log10K.push_back((-beta / alpha) * rs.equilibrium_const[temp] +
     670             :                                    redox_e_log10K[temp]);
     671         516 :         }
     672             :     }
     673             :   }
     674             :   // record the above in the model.redox_stoichiometry and model.redox_log10K DenseMatrices
     675        1155 :   const unsigned num_redox = redox_stoi.size() / num_cols;
     676        1155 :   _model.redox_stoichiometry.resize(num_redox, num_cols);
     677        1783 :   for (unsigned red = 0; red < num_redox; ++red)
     678        6702 :     for (unsigned basis_i = 0; basis_i < num_cols; ++basis_i)
     679        6074 :       _model.redox_stoichiometry(red, basis_i) = redox_stoi[red * num_cols + basis_i];
     680        1155 :   _model.redox_log10K.resize(num_redox, num_temperatures);
     681        1783 :   for (unsigned red = 0; red < num_redox; ++red)
     682        5652 :     for (unsigned temp = 0; temp < num_temperatures; ++temp)
     683        5024 :       _model.redox_log10K(red, temp) = redox_log10K[red * num_temperatures + temp];
     684             : 
     685             :   // To build the kin_species_index, kin_species_name, etc, let's build an "overlap", similar to
     686             :   // above
     687        1155 :   std::vector<GeochemistryMineralSpecies> overlap_kin(_kinetic_mineral_info);
     688        1200 :   for (const auto & species : _kinetic_redox_info)
     689             :   {
     690             :     GeochemistryMineralSpecies ms;
     691          45 :     ms.name = species.name;
     692          45 :     ms.molecular_volume = 0.0;
     693             :     ms.basis_species = species.basis_species;
     694          45 :     ms.molecular_weight = species.molecular_weight;
     695          45 :     ms.equilibrium_const = species.equilibrium_const;
     696          45 :     overlap_kin.push_back(ms);
     697          45 :   }
     698        1173 :   for (const auto & species : _kinetic_surface_info)
     699             :   {
     700             :     GeochemistryMineralSpecies ms;
     701          18 :     ms.name = species.name;
     702          18 :     ms.molecular_volume = 0.0;
     703             :     ms.basis_species = species.basis_species;
     704          18 :     ms.molecular_weight = species.molecular_weight;
     705          18 :     const Real T0 = _db.getTemperatures()[0];
     706         162 :     for (const auto & temp : _db.getTemperatures())
     707         144 :       ms.equilibrium_const.push_back(species.log10K + species.dlog10KdT * (temp - T0));
     708          18 :     overlap_kin.push_back(ms);
     709          18 :   }
     710        1155 :   const unsigned num_kin = overlap_kin.size();
     711             : 
     712             :   // create the kin_species_index map
     713             :   ind = 0;
     714        1464 :   for (const auto & species : overlap_kin)
     715         309 :     _model.kin_species_index[species.name] = ind++;
     716             : 
     717             :   // extract the names
     718        1155 :   _model.kin_species_name = std::vector<std::string>(num_kin);
     719        1464 :   for (const auto & species : _model.kin_species_index)
     720         309 :     _model.kin_species_name[species.second] = species.first;
     721             : 
     722             :   // build the kin_species_mineral info
     723        2310 :   _model.kin_species_mineral = std::vector<bool>(num_kin, true);
     724        1200 :   for (const auto & species : _kinetic_redox_info)
     725          45 :     _model.kin_species_mineral[_model.kin_species_index[species.name]] = false;
     726        1173 :   for (const auto & species : _kinetic_surface_info)
     727          18 :     _model.kin_species_mineral[_model.kin_species_index[species.name]] = false;
     728             : 
     729             :   // create the kin_species_transported vector (false for minerals and surface species)
     730        2310 :   _model.kin_species_transported = std::vector<bool>(num_kin, true);
     731        1401 :   for (const auto & species : _kinetic_mineral_info)
     732         246 :     _model.kin_species_transported[_model.kin_species_index.at(species.name)] = false;
     733        1173 :   for (const auto & species : _kinetic_surface_info)
     734          18 :     _model.kin_species_transported[_model.kin_species_index.at(species.name)] = false;
     735             : 
     736             :   // build the kin_species_charge info
     737        2310 :   _model.kin_species_charge = std::vector<Real>(num_kin, 0.0);
     738        1200 :   for (const auto & species : _kinetic_redox_info)
     739          45 :     _model.kin_species_charge[_model.kin_species_index[species.name]] = species.charge;
     740        1173 :   for (const auto & species : _kinetic_surface_info)
     741          18 :     _model.kin_species_charge[_model.kin_species_index[species.name]] = species.charge;
     742             : 
     743             :   // extract the molecular weight
     744        1155 :   _model.kin_species_molecular_weight = std::vector<Real>(num_kin, 0.0);
     745        1464 :   for (const auto & species : overlap_kin)
     746         309 :     _model.kin_species_molecular_weight[_model.kin_species_index[species.name]] =
     747         309 :         species.molecular_weight;
     748             : 
     749             :   // extract the molecular volume
     750        1155 :   _model.kin_species_molecular_volume = std::vector<Real>(num_kin, 0.0);
     751        1464 :   for (const auto & species : overlap_kin)
     752         309 :     _model.kin_species_molecular_volume[_model.kin_species_index[species.name]] =
     753         309 :         species.molecular_volume;
     754             : 
     755             :   // extract the stoichiometry
     756        1155 :   _model.kin_stoichiometry.resize(num_kin, num_cols);
     757        1155 :   _model.kin_log10K.resize(num_kin, num_temperatures);
     758             : 
     759             :   // populate the stoichiometry
     760        1464 :   for (const auto & species : overlap_kin)
     761             :   {
     762         309 :     const unsigned row = _model.kin_species_index[species.name];
     763        2781 :     for (unsigned i = 0; i < num_temperatures; ++i)
     764        2472 :       _model.kin_log10K(row, i) = species.equilibrium_const[i];
     765        1267 :     for (const auto & react : species.basis_species)
     766             :     {
     767         958 :       const Real stoi_coeff = react.second;
     768         958 :       if (_model.basis_species_index.count(react.first) == 1)
     769             :       {
     770         922 :         const unsigned col = _model.basis_species_index[react.first];
     771         922 :         _model.kin_stoichiometry(row, col) += react.second;
     772             :       }
     773             :       else if (_model.eqm_species_index.count(react.first) == 1)
     774             :       {
     775             :         // reaction species is not a basis component, but a secondary component.
     776             :         // So express stoichiometry in terms of the secondary component's reaction
     777          36 :         const unsigned sec_row = _model.eqm_species_index[react.first];
     778         324 :         for (unsigned i = 0; i < num_temperatures; ++i)
     779         288 :           _model.kin_log10K(row, i) += stoi_coeff * _model.eqm_log10K(sec_row, i);
     780         344 :         for (unsigned col = 0; col < num_cols; ++col)
     781         308 :           _model.kin_stoichiometry(row, col) += stoi_coeff * _model.eqm_stoichiometry(sec_row, col);
     782             :       }
     783             :       else
     784           0 :         mooseError("Kinetic species " + species.name + " includes " + react.first +
     785             :                    ", which cannot be expressed in terms of the basis.  Previous checks must be "
     786             :                    "erroneous!");
     787             :     }
     788             :   }
     789             : 
     790             :   // check that there are no repeated sorbing sites in the SurfaceComplexationInfo
     791             :   std::vector<std::string> all_sorbing_sites;
     792        1262 :   for (const auto & name_info : _model.surface_complexation_info)
     793         322 :     for (const auto & name_frac : name_info.second.sorption_sites)
     794         215 :       if (std::find(all_sorbing_sites.begin(), all_sorbing_sites.end(), name_frac.first) !=
     795             :           all_sorbing_sites.end())
     796           1 :         mooseError(
     797             :             "The sorbing site ", name_frac.first, " appears in more than one sorbing mineral");
     798             :       else
     799         214 :         all_sorbing_sites.push_back(name_frac.first);
     800             : 
     801             :   // build the information related to surface sorption, and modify the species_transported vectors
     802        1154 :   _model.surface_sorption_related.assign(num_rows, false);
     803        1154 :   _model.surface_sorption_number.assign(num_rows, 99);
     804        1154 :   for (const auto & name_info :
     805        1259 :        _model.surface_complexation_info) // all minerals involved in surface complexation
     806             :   {
     807         106 :     for (const auto & name_frac :
     808         318 :          name_info.second.sorption_sites) // all sorption sites on the given mineral
     809             :     {
     810         212 :       const unsigned basis_index_of_sorption_site = _model.basis_species_index.at(name_frac.first);
     811             :       _model.basis_species_transported[basis_index_of_sorption_site] = false;
     812             :     }
     813             :     bool mineral_involved_in_eqm = false;
     814         106 :     for (const auto & name_frac :
     815         318 :          name_info.second.sorption_sites) // all sorption sites on the given mineral
     816             :     {
     817         212 :       const unsigned basis_index_of_sorption_site = _model.basis_species_index.at(name_frac.first);
     818        1253 :       for (unsigned j = 0; j < num_rows; ++j) // all equilibrium species
     819        1225 :         if (_model.eqm_stoichiometry(j, basis_index_of_sorption_site) != 0.0)
     820             :         {
     821             :           mineral_involved_in_eqm = true;
     822             :           break;
     823             :         }
     824             :     }
     825         106 :     if (!mineral_involved_in_eqm)
     826           4 :       continue;
     827         102 :     const unsigned num_surface_sorption = _model.surface_sorption_name.size();
     828         102 :     _model.surface_sorption_name.push_back(name_info.first);
     829         102 :     _model.surface_sorption_area.push_back(name_info.second.surface_area);
     830         102 :     for (const auto & name_frac :
     831         305 :          name_info.second.sorption_sites) // all sorption sites on the given mineral
     832             :     {
     833         204 :       const unsigned basis_index_of_sorption_site = _model.basis_species_index.at(name_frac.first);
     834        1920 :       for (unsigned j = 0; j < num_rows; ++j) // all equilibrium species
     835        1717 :         if (_model.eqm_stoichiometry(j, basis_index_of_sorption_site) != 0.0)
     836             :         {
     837         413 :           if (_model.surface_sorption_related[j])
     838           1 :             mooseError("It is an error for any equilibrium species (such as ",
     839             :                        _model.eqm_species_name[j],
     840             :                        ") to have a reaction involving more than one sorbing site");
     841             :           _model.surface_sorption_related[j] = true;
     842         412 :           _model.surface_sorption_number[j] = num_surface_sorption;
     843             :           _model.eqm_species_transported[j] = false;
     844             :         }
     845             :     }
     846             :   }
     847        2828 : }
     848             : 
     849             : const ModelGeochemicalDatabase &
     850      112153 : PertinentGeochemicalSystem::modelGeochemicalDatabase() const
     851             : {
     852      112153 :   return _model;
     853             : }
     854             : 
     855             : void
     856         111 : PertinentGeochemicalSystem::addKineticRate(const KineticRateUserDescription & description)
     857             : {
     858         111 :   const std::string kinetic_species = description.kinetic_species_name;
     859             :   if (_model.kin_species_index.count(kinetic_species) == 0)
     860           1 :     mooseError("Cannot prescribe a kinetic rate to species ",
     861             :                kinetic_species,
     862             :                " since it is not a kinetic species");
     863         110 :   const unsigned kinetic_species_index = _model.kin_species_index.at(kinetic_species);
     864             : 
     865             :   // build the promoting index list
     866         110 :   const unsigned num_pro = description.promoting_species.size();
     867         110 :   const unsigned num_basis = _model.basis_species_name.size();
     868         110 :   const unsigned num_eqm = _model.eqm_species_name.size();
     869         113 :   std::vector<Real> promoting_ind(num_basis + num_eqm, 0.0);
     870         112 :   std::vector<Real> promoting_m_ind(num_basis + num_eqm, 0.0);
     871         112 :   std::vector<Real> promoting_k(num_basis + num_eqm, 0.0);
     872         247 :   for (unsigned i = 0; i < num_pro; ++i)
     873             :   {
     874             :     unsigned index = 0;
     875         138 :     const std::string promoting_species = description.promoting_species[i];
     876             :     if (_model.basis_species_index.count(promoting_species) == 1)
     877          95 :       index = _model.basis_species_index.at(promoting_species);
     878             :     else if (_model.eqm_species_index.count(promoting_species) == 1)
     879          42 :       index = num_basis + _model.eqm_species_index.at(promoting_species);
     880             :     else
     881           1 :       mooseError(
     882             :           "Promoting species ", promoting_species, " must be a basis or a secondary species");
     883         137 :     promoting_ind[index] = description.promoting_indices[i];
     884         137 :     promoting_m_ind[index] = description.promoting_monod_indices[i];
     885         137 :     promoting_k[index] = description.promoting_half_saturation[i];
     886             :   }
     887             :   unsigned progeny_num = 0;
     888         109 :   if (_model.basis_species_index.count(description.progeny) == 1)
     889         105 :     progeny_num = _model.basis_species_index.at(description.progeny);
     890             :   else if (_model.eqm_species_index.count(description.progeny) == 1)
     891           3 :     progeny_num = num_basis + _model.eqm_species_index.at(description.progeny);
     892             :   else
     893           1 :     mooseError("Progeny ", description.progeny, " must be a basis or a secondary species");
     894             : 
     895             :   // append the result to kin_rate
     896         218 :   _model.kin_rate.push_back(KineticRateDefinition(kinetic_species_index,
     897             :                                                   promoting_ind,
     898             :                                                   promoting_m_ind,
     899             :                                                   promoting_k,
     900             :                                                   progeny_num,
     901             :                                                   description));
     902         108 : }

Generated by: LCOV version 1.14