LCOV - code coverage report
Current view: top level - src/outputs - GeochemistryConsoleOutput.C (source / functions) Hit Total Coverage
Test: idaholab/moose geochemistry: 2bf808 Lines: 197 215 91.6 %
Date: 2025-07-17 01:29:12 Functions: 5 5 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 "GeochemistryConsoleOutput.h"
      11             : #include "GeochemistryConstants.h"
      12             : #include "GeochemistryFormattedOutput.h"
      13             : #include "GeochemistrySortedIndices.h"
      14             : 
      15             : registerMooseObject("GeochemistryApp", GeochemistryConsoleOutput);
      16             : 
      17             : InputParameters
      18        1469 : GeochemistryConsoleOutput::sharedParams()
      19             : {
      20        1469 :   InputParameters params = emptyInputParameters();
      21        2938 :   params.addParam<unsigned int>("precision", 4, "Precision for printing values");
      22        2938 :   params.addParam<Real>(
      23             :       "mol_cutoff",
      24        2938 :       1E-40,
      25             :       "Information regarding species with molalities less than this amount will not be outputted");
      26        2938 :   params.addParam<bool>(
      27             :       "solver_info",
      28        2938 :       false,
      29             :       "Print information (to the console) from the solver including residuals, swaps, etc");
      30        1469 :   return params;
      31           0 : }
      32             : 
      33             : InputParameters
      34         846 : GeochemistryConsoleOutput::validParams()
      35             : {
      36         846 :   InputParameters params = Output::validParams();
      37         846 :   params += GeochemistryConsoleOutput::sharedParams();
      38        1692 :   params.addRequiredParam<UserObjectName>("geochemistry_reactor",
      39             :                                           "The name of the GeochemistryReactor UserObject");
      40        2538 :   params.addRangeCheckedParam<Real>("stoichiometry_tolerance",
      41        1692 :                                     1E-6,
      42             :                                     "stoichiometry_tolerance >= 0.0",
      43             :                                     "if abs(any stoichiometric coefficient) < stoi_tol then it is "
      44             :                                     "set to zero, and so will not appear in the output");
      45        1692 :   params.addRequiredParam<UserObjectName>(
      46             :       "nearest_node_number_UO",
      47             :       "The NearestNodeNumber UserObject that defines the physical point at which to query the "
      48             :       "GeochemistryReactor");
      49         846 :   params.addClassDescription("Outputs results from a GeochemistryReactor at a particular point");
      50         846 :   return params;
      51           0 : }
      52             : 
      53         423 : GeochemistryConsoleOutput::GeochemistryConsoleOutput(const InputParameters & parameters)
      54             :   : Output(parameters),
      55             :     UserObjectInterface(this),
      56         423 :     _reactor(getUserObject<GeochemistryReactorBase>("geochemistry_reactor")),
      57         423 :     _nnn(getUserObject<NearestNodeNumberUO>("nearest_node_number_UO")),
      58         846 :     _precision(getParam<unsigned int>("precision")),
      59         846 :     _stoi_tol(getParam<Real>("stoichiometry_tolerance")),
      60         846 :     _solver_info(getParam<bool>("solver_info")),
      61        1269 :     _mol_cutoff(getParam<Real>("mol_cutoff"))
      62             : {
      63         423 : }
      64             : 
      65             : void
      66         506 : GeochemistryConsoleOutput::output()
      67             : {
      68         506 :   const Node * closest_node = _nnn.getClosestNode();
      69         506 :   if (!closest_node)
      70         192 :     return;
      71             :   const dof_id_type closest_id = closest_node->id();
      72             : 
      73         314 :   if (_solver_info)
      74         435 :     _console << _reactor.getSolverOutput(closest_id).str();
      75             : 
      76             :   // retrieve information
      77         314 :   const GeochemicalSystem & egs = _reactor.getGeochemicalSystem(closest_id);
      78         314 :   const unsigned num_basis = egs.getNumInBasis();
      79         314 :   const unsigned num_eqm = egs.getNumInEquilibrium();
      80         314 :   const unsigned num_kin = egs.getNumKinetic();
      81         314 :   const std::vector<Real> & basis_molality = egs.getSolventMassAndFreeMolalityAndMineralMoles();
      82         314 :   const std::vector<Real> & basis_activity = egs.getBasisActivity();
      83         314 :   const std::vector<Real> & basis_act_coef = egs.getBasisActivityCoefficient();
      84         314 :   const std::vector<Real> & bulk_moles = egs.getBulkMolesOld();
      85         314 :   const std::vector<Real> & eqm_molality = egs.getEquilibriumMolality();
      86         314 :   const std::vector<Real> & eqm_act_coef = egs.getEquilibriumActivityCoefficient();
      87         314 :   const std::vector<Real> & eqm_SI = egs.getSaturationIndices();
      88         314 :   const std::vector<Real> & kin_moles = egs.getKineticMoles();
      89         314 :   const ModelGeochemicalDatabase & mgd = egs.getModelGeochemicalDatabase();
      90             : 
      91         314 :   _console << std::setprecision(_precision);
      92             : 
      93         314 :   _console << "\nSummary:\n";
      94             : 
      95         314 :   _console << "Total number of iterations required = " << _reactor.getSolverIterations(closest_id)
      96         314 :            << "\n";
      97         314 :   _console << "Error in calculation = " << _reactor.getSolverResidual(closest_id) << "mol\n";
      98         314 :   _console << "Charge of solution = " << egs.getTotalChargeOld() << "mol";
      99         314 :   _console << " (charge-balance species = "
     100         314 :            << mgd.basis_species_name[egs.getChargeBalanceBasisIndex()] << ")\n";
     101             : 
     102         314 :   _console << "Mass of solvent water = " << basis_molality[0] << "kg\n";
     103             : 
     104         314 :   Real mass = bulk_moles[0] / GeochemistryConstants::MOLES_PER_KG_WATER;
     105        1757 :   for (unsigned i = 1; i < num_basis; ++i) // do not loop over water
     106        1443 :     mass += bulk_moles[i] * mgd.basis_species_molecular_weight[i] / 1000.0;
     107         314 :   _console << "Total mass = " << mass << "kg";
     108         314 :   if (num_kin == 0)
     109         264 :     _console << "\n";
     110             :   else
     111             :   {
     112          50 :     _console << " (including kinetic species and free minerals)\n";
     113         100 :     for (unsigned k = 0; k < num_kin; ++k)
     114          50 :       mass -= kin_moles[k] * mgd.kin_species_molecular_weight[k] / 1000.0;
     115          50 :     _console << "Mass without kinetic species but including free minerals = " << mass << "kg\n";
     116             :   }
     117             :   // remove the free minerals
     118        1757 :   for (unsigned i = 1; i < num_basis; ++i) // do not loop over water
     119        1443 :     if (mgd.basis_species_mineral[i])
     120          90 :       mass -= basis_molality[i] * mgd.basis_species_molecular_weight[i] / 1000.0;
     121             :   // remove surface complexes
     122         314 :   for (const auto & name_info :
     123         369 :        mgd.surface_complexation_info) // all minerals involved in surface complexation
     124          55 :     for (const auto & name_frac :
     125         165 :          name_info.second.sorption_sites) // all sorption sites on the given mineral
     126             :     {
     127             :       const unsigned i =
     128         110 :           mgd.basis_species_index.at(name_frac.first); // i = basis_index_of_sorption_site
     129         110 :       mass -= basis_molality[i] * mgd.basis_species_molecular_weight[i] / 1000.0;
     130             :     }
     131         314 :   _console << "Mass of aqueous solution = " << mass << "kg";
     132         314 :   if (num_kin == 0)
     133         264 :     _console << " (without free minerals)\n";
     134             :   else
     135          50 :     _console << " (without kinetic species and without free minerals)\n";
     136             : 
     137             :   // Output the aqueous solution pH, if relevant
     138         628 :   if (mgd.basis_species_index.count("H+"))
     139         822 :     _console << "pH = " << -std::log10(basis_activity[mgd.basis_species_index.at("H+")]) << "\n";
     140         628 :   if (mgd.eqm_species_index.count("H+"))
     141          30 :     _console << "pH = "
     142         120 :              << -std::log10(eqm_molality[mgd.eqm_species_index.at("H+")] *
     143          90 :                             eqm_act_coef[mgd.eqm_species_index.at("H+")])
     144          30 :              << "\n";
     145             : 
     146             :   // Output the aqueous solution pe, if relevant
     147         314 :   if (mgd.redox_stoichiometry.m() > 0)
     148          50 :     _console << "pe = " << egs.getRedoxLog10K(0) - egs.log10RedoxActivityProduct(0) << "\n";
     149             : 
     150             :   // Output ionic strengths
     151         314 :   _console << "Ionic strength = " << egs.getIonicStrength() << "mol/kg(solvent water)\n";
     152         314 :   _console << "Stoichiometric ionic strength = " << egs.getStoichiometricIonicStrength()
     153         314 :            << "mol/kg(solvent water)\n";
     154             : 
     155             :   // Output activity of water
     156         314 :   _console << "Activity of water = " << basis_activity[0] << "\n";
     157             : 
     158             :   // Output temperature
     159         314 :   _console << "Temperature = " << egs.getTemperature() << "\n";
     160             : 
     161             :   // Output the basis species information, sorted by molality
     162             :   std::vector<unsigned> basis_order =
     163         314 :       GeochemistrySortedIndices::sortedIndices(basis_molality, false);
     164         314 :   _console << "\nBasis Species:\n";
     165        2071 :   for (const auto & i : basis_order)
     166        1757 :     if (i == 0 || mgd.basis_species_gas[i])
     167         329 :       continue;
     168             :     else
     169             :     {
     170        1428 :       _console << mgd.basis_species_name[i] << ";  bulk_moles = " << bulk_moles[i]
     171        1428 :                << "mol;  bulk_conc = "
     172        1428 :                << bulk_moles[i] * mgd.basis_species_molecular_weight[i] * 1000.0 / mass
     173        1428 :                << "mg/kg(soln);";
     174        1428 :       if (!mgd.basis_species_mineral[i])
     175        1338 :         _console << "  molality = " << basis_molality[i] << "mol/kg(solvent water);  free_conc = "
     176        1338 :                  << basis_molality[i] * basis_molality[0] / mass *
     177        1338 :                         mgd.basis_species_molecular_weight[i] * 1000.0
     178        1338 :                  << "mg/kg(soln);  act_coeff = " << basis_act_coef[i]
     179        1338 :                  << ";  log10(a) = " << std::log10(basis_activity[i]) << "\n";
     180          90 :       else if (mgd.basis_species_mineral[i])
     181          90 :         _console << "  free_moles = " << basis_molality[i] << "mol;  free_mass = "
     182          90 :                  << basis_molality[i] * mgd.basis_species_molecular_weight[i] * 1000.0 << "mg\n";
     183             :     }
     184        2071 :   for (unsigned i = 0; i < num_basis; ++i)
     185        1757 :     if (mgd.basis_species_gas[i])
     186          15 :       _console << mgd.basis_species_name[i] << ";  fugacity = " << basis_activity[i] << "\n";
     187             : 
     188             :   // Output the equilibrium species info, sorted by molality
     189         314 :   std::vector<unsigned> eqm_order = GeochemistrySortedIndices::sortedIndices(eqm_molality, false);
     190         314 :   _console << "\nEquilibrium Species:\n";
     191        1922 :   for (const auto & i : eqm_order)
     192        1678 :     if (eqm_molality[i] <= _mol_cutoff)
     193             :       break;
     194        1608 :     else if (mgd.eqm_species_gas[i])
     195           0 :       continue;
     196             :     else
     197        1608 :       _console << mgd.eqm_species_name[i] << ";  molality = " << eqm_molality[i]
     198        1608 :                << "mol/kg(solvent water);  free_conc = "
     199        1608 :                << eqm_molality[i] * basis_molality[0] / mass * mgd.eqm_species_molecular_weight[i] *
     200        1608 :                       1000.0
     201        1608 :                << "mg/kg(soln);  act_coeff = " << eqm_act_coef[i]
     202        1608 :                << ";  log10(a) = " << std::log10(eqm_molality[i] * eqm_act_coef[i]) << ";  "
     203        1608 :                << mgd.eqm_species_name[i] << " = "
     204        1608 :                << GeochemistryFormattedOutput::reaction(
     205        1608 :                       mgd.eqm_stoichiometry, i, mgd.basis_species_name, _stoi_tol, _precision)
     206        1608 :                << ";  log10K = " << egs.getLog10K(i) << "\n";
     207        6472 :   for (unsigned i = 0; i < num_eqm; ++i)
     208        6158 :     if (mgd.eqm_species_gas[i])
     209             :     {
     210             :       Real log10f = 0;
     211           0 :       for (unsigned basis_i = 0; basis_i < num_basis; ++basis_i)
     212           0 :         log10f += mgd.eqm_stoichiometry(i, basis_i) * std::log10(basis_activity[basis_i]);
     213           0 :       log10f -= egs.getLog10K(i);
     214           0 :       _console << mgd.eqm_species_name[i]
     215           0 :                << ";  act_coeff = " << egs.getEquilibriumActivityCoefficient(i)
     216           0 :                << ";  fugacity = " << std::pow(10.0, log10f) << ";  " << mgd.eqm_species_name[i]
     217           0 :                << " = "
     218           0 :                << GeochemistryFormattedOutput::reaction(
     219           0 :                       mgd.eqm_stoichiometry, i, mgd.basis_species_name, _stoi_tol, _precision)
     220           0 :                << ";  log10K = " << egs.getLog10K(i) << "\n";
     221             :     }
     222             : 
     223             :   // Output the kinetic species information, sorted by mole number
     224         314 :   std::vector<unsigned> kin_order = GeochemistrySortedIndices::sortedIndices(kin_moles, false);
     225         314 :   _console << "\nKinetic Species:\n";
     226         364 :   for (const auto & k : kin_order)
     227             :   {
     228          50 :     _console << mgd.kin_species_name[k] << ";  moles = " << kin_moles[k]
     229          50 :              << ";  mass = " << kin_moles[k] * mgd.kin_species_molecular_weight[k] * 1000.0
     230          50 :              << "mg;  ";
     231          50 :     if (mgd.kin_species_mineral[k])
     232          50 :       _console << "volume = " << kin_moles[k] * mgd.kin_species_molecular_volume[k] << "cm^3;  ";
     233          50 :     _console << mgd.kin_species_name[k] << " = "
     234          50 :              << GeochemistryFormattedOutput::reaction(
     235          50 :                     mgd.kin_stoichiometry, k, mgd.basis_species_name, _stoi_tol, _precision)
     236          50 :              << ";  log10(Q) = " << egs.log10KineticActivityProduct(k)
     237          50 :              << ";  log10K = " << egs.getKineticLog10K(k)
     238         100 :              << ";  dissolution_rate*dt = " << -_reactor.getMoleAdditions(closest_id)(num_basis + k)
     239          50 :              << "\n";
     240             :   }
     241             : 
     242             :   // Output the mineral info, sorted by saturation indices
     243         314 :   std::vector<unsigned> mineral_order = GeochemistrySortedIndices::sortedIndices(eqm_SI, false);
     244         314 :   _console << "\nMinerals:\n";
     245        6472 :   for (const auto & i : mineral_order)
     246        6158 :     if (mgd.eqm_species_mineral[i])
     247         325 :       _console << mgd.eqm_species_name[i] << " = "
     248         325 :                << GeochemistryFormattedOutput::reaction(
     249         325 :                       mgd.eqm_stoichiometry, i, mgd.basis_species_name, _stoi_tol, _precision)
     250         325 :                << ";  log10K = " << egs.getLog10K(i) << ";  SI = " << eqm_SI[i] << "\n";
     251             : 
     252             :   // Output the Nernst potentials, if relevant
     253         314 :   _console << "\nNernst potentials:\n";
     254         314 :   if (mgd.redox_stoichiometry.m() > 0)
     255          50 :     outputNernstInfo(egs);
     256             : 
     257         314 :   const unsigned num_pot = egs.getNumSurfacePotentials();
     258         314 :   if (num_pot > 0)
     259             :   {
     260          55 :     _console << "\nSorbing surfaces:\n";
     261          55 :     const std::vector<Real> area = egs.getSorbingSurfaceArea();
     262         110 :     for (unsigned sp = 0; sp < num_pot; ++sp)
     263          55 :       _console << mgd.surface_sorption_name[sp] << "; area = " << area[sp]
     264          55 :                << "m^2; specific charge = " << egs.getSurfaceCharge(sp)
     265          55 :                << "C/m^2; surface potential = " << egs.getSurfacePotential(sp) << "V\n";
     266             :   }
     267             : 
     268         314 :   DenseVector<Real> bulk_in_original_basis = egs.getBulkOldInOriginalBasis();
     269         314 :   DenseVector<Real> transported_bulk_in_original_basis = egs.getTransportedBulkInOriginalBasis();
     270             :   std::vector<std::string> original_basis_names =
     271         314 :       _reactor.getPertinentGeochemicalSystem().originalBasisNames();
     272         314 :   _console << "\nIn original basis:\n";
     273        2071 :   for (unsigned i = 0; i < num_basis; ++i)
     274        1757 :     _console << original_basis_names[i] << ";  total_bulk_moles = " << bulk_in_original_basis(i)
     275        1757 :              << ";  transported_bulk_moles = " << transported_bulk_in_original_basis(i) << "\n";
     276             : 
     277         314 :   _console << std::flush;
     278         314 : }
     279             : 
     280             : void
     281          50 : GeochemistryConsoleOutput::outputNernstInfo(const GeochemicalSystem & egs_ref) const
     282             : {
     283             :   // Copy egs_ref so we can call non-const methods (viz, swaps).  This does not copy the data in
     284             :   // egs.getModelGeochemicalDatabase(), only the reference (both references refer to the same
     285             :   // block of memory) and unfortunately the swaps below manipulate that memory
     286          50 :   GeochemicalSystem egs = egs_ref;
     287             :   // Since we only want to do the swaps to print out some Nernst info, but don't want to impact
     288             :   // the rest of the simulation, copy the mgd, so that we can copy it back into the aforementioned
     289             :   // block of memory at the end of this method
     290          50 :   ModelGeochemicalDatabase mgd_without_nernst_swaps = egs.getModelGeochemicalDatabase();
     291             : 
     292          50 :   const ModelGeochemicalDatabase & mgd_ref = egs.getModelGeochemicalDatabase();
     293             :   // attempt to undo the swaps that have been done to mgd_ref.
     294             :   // NOTE FOR FUTURE: In the current code (2020, June 1) swapping the redox stuff in mgd seems
     295             :   // redundant.  While the current code does these swaps, here we just undo all the swaps again to
     296             :   // write the redox stuff in the original basis.  Since this is the only place that the redox
     297             :   // stuff is used, doing any swaps on the redox stuff is currently just a waste of time! take a
     298             :   // copy of the following because they get modified during the swaps
     299          50 :   const std::vector<unsigned> have_swapped_out_of_basis = mgd_ref.have_swapped_out_of_basis;
     300          50 :   const std::vector<unsigned> have_swapped_into_basis = mgd_ref.have_swapped_into_basis;
     301         150 :   for (int sw = have_swapped_out_of_basis.size() - 1; sw >= 0; --sw)
     302             :   {
     303             :     // Don't check for gases being swapped in or out of the basis (which usually can't happen in
     304             :     // the middle of a Newton process) because we're going to trash all the swaps at the end of
     305             :     // this method, and we don't have to worry about bulk moles, etc: we just want the
     306             :     // stoichiometries and the activities
     307             :     try
     308             :     {
     309         100 :       egs.performSwapNoCheck(have_swapped_out_of_basis[sw], have_swapped_into_basis[sw]);
     310             :     }
     311           0 :     catch (const MooseException & e)
     312             :     {
     313           0 :       const std::string to_swap_in = mgd_ref.eqm_species_name[have_swapped_into_basis[sw]];
     314           0 :       const std::string to_swap_out = mgd_ref.basis_species_name[have_swapped_out_of_basis[sw]];
     315             :       // Don't crash the entire simulation, just because Nernst-related swapping does not work
     316           0 :       mooseWarning("Swapping ", to_swap_out, " and ", to_swap_in, ": ", e.what());
     317           0 :     }
     318             :   }
     319             : 
     320          50 :   const Real prefactor = -GeochemistryConstants::LOGTEN * GeochemistryConstants::GAS_CONSTANT *
     321          50 :                          (egs.getTemperature() + GeochemistryConstants::CELSIUS_TO_KELVIN) /
     322          50 :                          GeochemistryConstants::FARADAY;
     323             : 
     324          50 :   if (mgd_ref.redox_lhs == egs_ref.getOriginalRedoxLHS())
     325             :   {
     326          50 :     std::vector<Real> eh(mgd_ref.redox_stoichiometry.m());
     327         140 :     for (unsigned red = 0; red < mgd_ref.redox_stoichiometry.m(); ++red)
     328          90 :       eh[red] = prefactor * (egs.log10RedoxActivityProduct(red) - egs.getRedoxLog10K(red));
     329          50 :     const std::vector<unsigned> eh_order = GeochemistrySortedIndices::sortedIndices(eh, false);
     330         140 :     for (const auto & red : eh_order)
     331          90 :       _console << mgd_ref.redox_lhs << " = "
     332          90 :                << GeochemistryFormattedOutput::reaction(mgd_ref.redox_stoichiometry,
     333             :                                                         red,
     334          90 :                                                         mgd_ref.basis_species_name,
     335          90 :                                                         _stoi_tol,
     336          90 :                                                         _precision)
     337          90 :                << ";  Eh = " << eh[red] << "V\n";
     338             :   }
     339             : 
     340          50 :   _console << std::flush;
     341             : 
     342             :   // restore the original mgd by copying the data in copy_of_mgd into the memory referenced by
     343             :   // egs.getModelGeochemicalDatabase()
     344          50 :   egs.setModelGeochemicalDatabase(mgd_without_nernst_swaps);
     345          50 : }

Generated by: LCOV version 1.14