https://mooseframework.inl.gov
GeochemicalModelInterrogator.C
Go to the documentation of this file.
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 
13 #include <limits>
14 
16 
19 {
21  params.addRequiredParam<UserObjectName>("model_definition",
22  "The name of the GeochemicalModelDefinition user object");
23  params.addParam<std::vector<std::string>>(
24  "swap_out_of_basis",
25  {},
26  "Species that should be removed from the model_definition's basis and be replaced with the "
27  "swap_into_basis species. There must be the same number of species in swap_out_of_basis and "
28  "swap_into_basis. If this list contains more than one species, the swapping is performed "
29  "one-by-one, starting with the first pair (swap_out_of_basis[0] and swap_into_basis[0]), "
30  "then the next pair, etc");
31  params.addParam<std::vector<std::string>>(
32  "swap_into_basis",
33  {},
34  "Species that should be removed from the model_definition's "
35  "equilibrium species list and added to the basis");
36  params.addParam<std::vector<std::string>>(
37  "activity_species",
38  {},
39  "Species that are provided numerical values of activity (or fugacity for gases) in the "
40  "activity_value input");
41  params.addParam<std::vector<Real>>(
42  "activity_values",
43  {},
44  "Numerical values for the activity (or fugacity) for the "
45  "species in the activity_species list. These are activity values, not log10(activity).");
46  params.addParam<unsigned int>(
47  "precision",
48  4,
49  "Precision for printing values. Also, if the absolute value of a stoichiometric coefficient "
50  "is less than 10^(-precision) then it is set to zero. Also, if equilibrium temperatures are "
51  "desired, they will be computed to a relative error of 10^(-precision)");
52  params.addParam<std::string>("equilibrium_species",
53  "",
54  "Only output results for this equilibrium species. If not "
55  "provided, results for all equilibrium species will be outputted");
56  MooseEnum interrogation_choice("reaction activity eqm_temperature", "reaction");
57  params.addParam<MooseEnum>(
58  "interrogation",
59  interrogation_choice,
60  "Type of interrogation to perform. reaction: Output equilibrium species reactions and "
61  "log10K. activity: determine activity products at equilibrium. eqm_temperature: determine "
62  "temperature to ensure equilibrium");
63  params.addParam<Real>(
64  "temperature",
65  25,
66  "Equilibrium constants will be computed at this temperature [degC]. This is "
67  "ignored if interrogation=eqm_temperature.");
68  params.addRangeCheckedParam<Real>(
69  "stoichiometry_tolerance",
70  1E-6,
71  "stoichiometry_tolerance >= 0.0",
72  "Swapping involves inverting matrices via a singular value decomposition. During this "
73  "process: (1) if abs(singular value) < stoi_tol * L1norm(singular values), then the "
74  "matrix is deemed singular (so the basis swap is deemed invalid); (2) if abs(any "
75  "stoichiometric coefficient) < stoi_tol then it is set to zero.");
76  return params;
77 }
78 
81 {
84  params.addClassDescription("Performing simple manipulations of and querying a "
85  "geochemical model");
86 
87  params.set<ExecFlagEnum>("execute_on") = {EXEC_FINAL};
88  return params;
89 }
90 
92  : Output(parameters),
93  UserObjectInterface(this),
94  _mgd(getUserObject<GeochemicalModelDefinition>("model_definition").getDatabase()),
95  _swapper(_mgd.basis_species_index.size(), getParam<Real>("stoichiometry_tolerance")),
96  _swap_out(getParam<std::vector<std::string>>("swap_out_of_basis")),
97  _swap_in(getParam<std::vector<std::string>>("swap_into_basis")),
98  _precision(getParam<unsigned int>("precision")),
99  _interrogation(getParam<MooseEnum>("interrogation").getEnum<InterrogationChoiceEnum>()),
100  _temperature(getParam<Real>("temperature")),
101  _activity_species(getParam<std::vector<std::string>>("activity_species")),
102  _activity_values(getParam<std::vector<Real>>("activity_values")),
103  _equilibrium_species(getParam<std::string>("equilibrium_species"))
104 {
105  if (_swap_out.size() != _swap_in.size())
106  paramError("swap_out_of_basis must have same length as swap_into_basis");
107  if (_activity_species.size() != _activity_values.size())
108  paramError("activity_species must have same length as activity_values");
109 }
110 
111 void
113 {
114  for (const auto & sp : eqmSpeciesOfInterest())
115  {
116  switch (_interrogation)
117  {
119  outputReaction(sp);
120  break;
122  outputActivity(sp);
123  break;
125  outputTemperature(sp);
126  break;
127  }
128  }
129 
130  for (unsigned i = 0; i < _swap_out.size(); ++i)
131  {
132  // any exception here is an error
133  try
134  {
136  }
137  catch (const MooseException & e)
138  {
139  mooseError(e.what());
140  }
141  for (const auto & sp : eqmSpeciesOfInterest())
142  {
143  switch (_interrogation)
144  {
146  outputReaction(sp);
147  break;
149  outputActivity(sp);
150  break;
152  outputTemperature(sp);
153  break;
154  }
155  }
156  }
157 }
158 
159 std::vector<std::string>
161 {
162  if (_equilibrium_species == "")
163  return _mgd.eqm_species_name;
164  else if (_mgd.eqm_species_index.count(_equilibrium_species) == 1)
165  return {_equilibrium_species};
166  return {};
167 }
168 
169 void
170 GeochemicalModelInterrogator::outputReaction(const std::string & eqm_species) const
171 {
172  if (_mgd.eqm_species_index.count(eqm_species) == 0)
173  return;
174  std::stringstream ss;
175  const unsigned row = _mgd.eqm_species_index.at(eqm_species);
176  const Real cutoff = std::pow(10.0, -1.0 * _precision);
177  const std::vector<Real> temps = _mgd.original_database->getTemperatures();
178  const unsigned numT = temps.size();
179  const std::string model_type = _mgd.original_database->getLogKModel();
181  temps, _mgd.eqm_log10K.sub_matrix(row, 1, 0, numT).get_values(), model_type);
182  log10K.generate();
183  const Real log10_eqm_const = log10K.sample(_temperature);
184  ss << std::setprecision(_precision);
185  ss << eqm_species << " = ";
188  ss << " . log10(K) = " << log10_eqm_const;
189  ss << std::endl;
190  _console << ss.str() << std::flush;
191 }
192 
193 bool
194 GeochemicalModelInterrogator::knownActivity(const std::string & species) const
195 {
196  for (const auto & sp : _activity_species)
197  if (sp == species)
198  return true;
199  if (_mgd.basis_species_index.count(species))
201  if (_mgd.eqm_species_index.count(species))
202  return _mgd.eqm_species_mineral[_mgd.eqm_species_index.at(species)];
203  return false;
204 }
205 
206 Real
207 GeochemicalModelInterrogator::getActivity(const std::string & species) const
208 {
209  unsigned ind = 0;
210  for (const auto & sp : _activity_species)
211  {
212  if (sp == species)
213  return _activity_values[ind];
214  ind += 1;
215  }
216  return 1.0; // must be a mineral
217 }
218 
219 void
220 GeochemicalModelInterrogator::outputActivity(const std::string & eqm_species) const
221 {
222  if (_mgd.eqm_species_index.count(eqm_species) == 0)
223  return;
224 
225  const unsigned row = _mgd.eqm_species_index.at(eqm_species);
226  const unsigned num_cols = _mgd.basis_species_index.size();
227  const Real cutoff = std::pow(10.0, -1.0 * _precision);
228  const std::vector<Real> temps = _mgd.original_database->getTemperatures();
229  const unsigned numT = temps.size();
230  const std::string model_type = _mgd.original_database->getLogKModel();
232  temps, _mgd.eqm_log10K.sub_matrix(row, 1, 0, numT).get_values(), model_type);
233  log10K.generate();
234  Real rhs = log10K.sample(_temperature);
235  std::stringstream lhs;
236 
237  lhs << std::setprecision(_precision);
238  if (knownActivity(eqm_species))
239  rhs += std::log10(getActivity(eqm_species));
240  else
241  lhs << "(A_" << eqm_species << ")^-1 ";
242 
243  for (unsigned i = 0; i < num_cols; ++i)
244  if (std::abs(_mgd.eqm_stoichiometry(row, i)) > cutoff)
245  {
246  const std::string sp = _mgd.basis_species_name[i];
247  if (knownActivity(sp))
248  rhs -= _mgd.eqm_stoichiometry(row, i) * std::log10(getActivity(sp));
249  else
250  lhs << "(A_" << sp << ")^" << _mgd.eqm_stoichiometry(row, i) << " ";
251  }
252 
253  lhs << "= 10^" << rhs << std::endl;
254  _console << lhs.str() << std::flush;
255 }
256 
257 void
258 GeochemicalModelInterrogator::outputTemperature(const std::string & eqm_species) const
259 {
260  if (_mgd.eqm_species_index.count(eqm_species) == 0)
261  return;
262 
263  const unsigned row = _mgd.eqm_species_index.at(eqm_species);
264  const unsigned num_cols = _mgd.basis_species_index.size();
265  const Real cutoff = std::pow(10.0, -1.0 * _precision);
266  Real rhs = 1.0;
267 
268  // check that we know all the required activities, otherwise compute the RHS
269  if (knownActivity(eqm_species))
270  rhs = -std::log10(getActivity(eqm_species));
271  else
272  {
273  _console << "Not enough activites known to compute equilibrium temperature for reaction\n "
274  << std::flush;
275  outputReaction(eqm_species);
276  return;
277  }
278 
279  for (unsigned i = 0; i < num_cols; ++i)
280  if (std::abs(_mgd.eqm_stoichiometry(row, i)) > cutoff)
281  {
282  const std::string sp = _mgd.basis_species_name[i];
283  if (knownActivity(sp))
284  rhs += _mgd.eqm_stoichiometry(row, i) * std::log10(getActivity(sp));
285  else
286  {
287  _console << "Not enough activites known to compute equilibrium temperature for reaction\n "
288  << std::flush;
289  outputReaction(eqm_species);
290  return;
291  }
292  }
293 
294  const Real tsoln = solveForT(
295  _mgd.eqm_log10K.sub_matrix(row, 1, 0, _mgd.original_database->getTemperatures().size()), rhs);
296  _console << eqm_species << ". T = " << tsoln << "degC" << std::endl;
297 }
298 
299 Real
300 GeochemicalModelInterrogator::solveForT(const DenseMatrix<Real> & reference_log10K, Real rhs) const
301 {
302  const std::vector<Real> temps = _mgd.original_database->getTemperatures();
303  const unsigned numT = temps.size();
304  const std::string model_type = _mgd.original_database->getLogKModel();
305 
306  // find the bracket that contains the rhs
307  unsigned bracket = 0;
308  for (bracket = 0; bracket < numT - 1; ++bracket)
309  {
310  if (reference_log10K(0, bracket) <= rhs && reference_log10K(0, bracket + 1) > rhs)
311  break;
312  else if (reference_log10K(0, bracket) >= rhs && reference_log10K(0, bracket + 1) < rhs)
313  break;
314  }
315 
316  if (bracket == numT - 1)
317  return std::numeric_limits<double>::quiet_NaN();
318 
319  EquilibriumConstantInterpolator log10K(temps, reference_log10K.get_values(), model_type);
320  log10K.generate();
321  // now do a Newton-Raphson to find T for which log10K.sample(T) = rhs
323  const Real small_delT =
325  Real del_temp = small_delT;
326  unsigned iter = 0;
327  while (std::abs(del_temp) >= small_delT && iter++ < 100)
328  {
329  Real residual = log10K.sample(temp) - rhs;
330  del_temp = -residual / log10K.sampleDerivative(temp);
331  temp += del_temp;
332  }
333  return temp;
334 }
std::vector< std::string > eqmSpeciesOfInterest() const
provide a list of the equilibrium species of interest to the Interrogator
Queries and performs simple manipulations on a geochemical model.
const std::vector< std::string > _swap_in
virtual const char * what() const
std::string reaction(const DenseMatrix< Real > &stoi, unsigned row, const std::vector< std::string > &names, Real stoi_tol=1.0E-6, int precision=4)
Returns a nicely formatted string corresponding to the reaction defined by the given row of the stoic...
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
constexpr Real CELSIUS_TO_KELVIN
const std::vector< std::string > _swap_out
static InputParameters sharedParams()
params that are shared with the AddGeochemicalModelInterrogatorAction
std::unordered_map< std::string, unsigned > basis_species_index
basis_species_index[name] = index of the basis species, within all ModelGeochemicalDatabase internal ...
Fit the equilibrium constant values read from the thermodynamic databse at specified temperature valu...
void outputActivity(const std::string &eqm_species) const
output activity info to console
T & set(const std::string &name, bool quiet_mode=false)
enum GeochemicalModelInterrogator::InterrogationChoiceEnum _interrogation
std::string getLogKModel() const
Get the equilibrium constant model type.
GeochemicalModelInterrogator(const InputParameters &parameters)
std::unordered_map< std::string, unsigned > eqm_species_index
eqm_species_index[name] = index of the equilibrium species (secondary aqueous species, redox couples in equilibrium with the aqueous solution, minerals in equilibrium with the aqueous solution, gases in equilibrium with the aqueous solution) within all ModelGeochemicalDatabase internal datastrcutres, with given name
Real solveForT(const DenseMatrix< Real > &reference_log10K, Real rhs) const
void addRequiredParam(const std::string &name, const std::string &doc_string)
InputParameters emptyInputParameters()
User object that parses a geochemical database file, and only retains information relevant to the cur...
DenseMatrix< Real > eqm_stoichiometry
eqm_stoichiometry(i, j) = stoichiometric coefficient for equilibrium species "i" in terms of the basi...
bool knownActivity(const std::string &species) const
return true iff the activity is known for the species (it is a mineral or the user has set the activi...
const std::vector< Real > & getTemperatures() const
Get the temperature points that the equilibrium constant is defined at.
virtual void generate()
void paramError(const std::string &param, Args... args) const
std::vector< T > & get_values()
const std::vector< std::string > _activity_species
DenseMatrix< Real > eqm_log10K
eqm_log10K(i, j) = log10(equilibrium constant) for i^th equilibrium species at the j^th temperature p...
void outputReaction(const std::string &eqm_species) const
output nicely-formatted reaction info to console
const GeochemicalDatabaseReader * original_database
a pointer to the original database used to build this ModelGeochemicalDatabase
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Real getActivity(const std::string &species) const
return the activity for the species. Note that knownActivity should be checked before calling getActi...
void performSwap(ModelGeochemicalDatabase &mgd, const std::string &replace_this, const std::string &with_this)
Check that replacing the named basis species with the named equilibrium species is valid...
std::vector< bool > basis_species_mineral
basis_species_mineral[j] = true iff the j^th basis species is a mineral
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
std::vector< bool > eqm_species_mineral
eqm_species_mineral[i] = true iff the i^th equilibrium species is a mineral
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const std::vector< Real > _activity_values
const ConsoleStream _console
void outputTemperature(const std::string &eqm_species) const
output temperature info to console
DenseMatrix sub_matrix(unsigned int row_id, unsigned int row_size, unsigned int col_id, unsigned int col_size) const
MooseUnits pow(const MooseUnits &, int)
const ExecFlagType EXEC_FINAL
static InputParameters validParams()
std::vector< std::string > eqm_species_name
eqm_species_name[i] = name of the i^th eqm species
void ErrorVector unsigned int
std::vector< std::string > basis_species_name
basis_species_name[j] = name of the j^th basis species
registerMooseObject("GeochemistryApp", GeochemicalModelInterrogator)
void bracket(std::function< Real(Real)> const &f, Real &x1, Real &x2)
Function to bracket a root of a given function.
Definition: BrentsMethod.C:17