https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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),
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
111void
113{
114 for (const auto & sp : eqmSpeciesOfInterest())
115 {
116 switch (_interrogation)
117 {
119 outputReaction(sp);
120 break;
122 outputActivity(sp);
123 break;
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;
153 break;
154 }
155 }
156 }
157}
158
159std::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
169void
170GeochemicalModelInterrogator::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
193bool
194GeochemicalModelInterrogator::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))
203 return false;
204}
205
206Real
207GeochemicalModelInterrogator::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
219void
220GeochemicalModelInterrogator::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
257void
258GeochemicalModelInterrogator::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(
296 _console << eqm_species << ". T = " << tsoln << "degC" << std::endl;
297}
298
299Real
300GeochemicalModelInterrogator::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
322 Real temp = _mgd.original_database->getTemperatures()[bracket + 1];
323 const Real small_delT =
324 (temp + GeochemistryConstants::CELSIUS_TO_KELVIN) * std::pow(10.0, -1.0 * _precision);
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}
registerMooseObject("GeochemistryApp", GeochemicalModelInterrogator)
InputParameters emptyInputParameters()
const ExecFlagType EXEC_FINAL
void ErrorVector unsigned int
const ConsoleStream _console
Fit the equilibrium constant values read from the thermodynamic databse at specified temperature valu...
Real sampleDerivative(Real T)
Sample derivative of function at temperature T.
const std::vector< Real > & getTemperatures() const
Get the temperature points that the equilibrium constant is defined at.
std::string getLogKModel() const
Get the equilibrium constant model type.
User object that parses a geochemical database file, and only retains information relevant to the cur...
Queries and performs simple manipulations on a geochemical model.
const std::vector< std::string > _swap_in
void outputActivity(const std::string &eqm_species) const
output activity info to console
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...
void outputReaction(const std::string &eqm_species) const
output nicely-formatted reaction info to console
enum GeochemicalModelInterrogator::InterrogationChoiceEnum _interrogation
static InputParameters sharedParams()
params that are shared with the AddGeochemicalModelInterrogatorAction
const std::vector< std::string > _swap_out
std::vector< std::string > eqmSpeciesOfInterest() const
provide a list of the equilibrium species of interest to the Interrogator
GeochemicalModelInterrogator(const InputParameters &parameters)
const std::vector< std::string > _activity_species
Real solveForT(const DenseMatrix< Real > &reference_log10K, Real rhs) const
const std::vector< Real > _activity_values
Real getActivity(const std::string &species) const
return the activity for the species. Note that knownActivity should be checked before calling getActi...
void outputTemperature(const std::string &eqm_species) const
output temperature info to console
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,...
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
T & set(const std::string &name, bool quiet_mode=false)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
virtual void generate()
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
virtual const char * what() const
static InputParameters validParams()
std::vector< T > & get_values()
DenseMatrix sub_matrix(unsigned int row_id, unsigned int row_size, unsigned int col_id, unsigned int col_size) 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...
std::vector< bool > eqm_species_mineral
eqm_species_mineral[i] = true iff the i^th equilibrium species is a mineral
DenseMatrix< Real > eqm_stoichiometry
eqm_stoichiometry(i, j) = stoichiometric coefficient for equilibrium species "i" in terms of the basi...
std::vector< bool > basis_species_mineral
basis_species_mineral[j] = true iff the j^th basis species is a mineral
std::unordered_map< std::string, unsigned > eqm_species_index
eqm_species_index[name] = index of the equilibrium species (secondary aqueous species,...
const GeochemicalDatabaseReader * original_database
a pointer to the original database used to build this ModelGeochemicalDatabase
std::vector< std::string > basis_species_name
basis_species_name[j] = name of the j^th basis species
std::unordered_map< std::string, unsigned > basis_species_index
basis_species_index[name] = index of the basis species, within all ModelGeochemicalDatabase internal ...
DenseMatrix< Real > eqm_log10K
eqm_log10K(i, j) = log10(equilibrium constant) for i^th equilibrium species at the j^th temperature p...
std::vector< std::string > eqm_species_name
eqm_species_name[i] = name of the i^th eqm species