OGS
PhreeqcKernel.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "PhreeqcKernel.h"
5
6#include <phreeqcpp/cxxKinetics.h>
7
8#include <cmath>
9#include <cstdlib>
10
11#include "BaseLib/Error.h"
14
15namespace ChemistryLib
16{
18{
20 MeshLib::Mesh const& mesh,
22 std::size_t const num_chemical_systems,
23 std::vector<std::pair<int, std::string>> const&
24 process_id_to_component_name_map,
25 std::string const& database,
26 AqueousSolution aqueous_solution,
27 std::unique_ptr<EquilibriumReactants>&& equilibrium_reactants,
28 std::unique_ptr<Kinetics>&& kinetic_reactants,
29 std::vector<ReactionRate>&& reaction_rates)
31 _initial_aqueous_solution(aqueous_solution.getInitialAqueousSolution()),
32 _aqueous_solution(aqueous_solution.castToBaseClassNoninitialized()),
33 _reaction_rates(std::move(reaction_rates))
34{
36
37 loadDatabase(database);
38
39 // solution
40 for (std::size_t chemical_system_id = 0;
41 chemical_system_id < num_chemical_systems;
42 ++chemical_system_id)
43 {
44 aqueous_solution.setChemicalSystemID(chemical_system_id);
45 Rxn_solution_map.emplace(chemical_system_id,
46 *aqueous_solution.castToBaseClass());
47 }
48 use.Set_solution_in(true);
49
50 // equilibrium reactants
51 if (equilibrium_reactants)
52 {
53 tidyEquilibriumReactants(*equilibrium_reactants);
54
55 for (std::size_t chemical_system_id = 0;
56 chemical_system_id < num_chemical_systems;
57 ++chemical_system_id)
58 {
59 equilibrium_reactants->setChemicalSystemID(chemical_system_id);
60 Rxn_pp_assemblage_map.emplace(
61 chemical_system_id, *equilibrium_reactants->castToBaseClass());
62 }
63 // explicitly release and delete equilibrium_reactants
64 equilibrium_reactants.reset(nullptr);
65
66 use.Set_pp_assemblage_in(true);
67 }
68
69 // kinetic reactants
70 if (kinetic_reactants)
71 {
72 for (std::size_t chemical_system_id = 0;
73 chemical_system_id < num_chemical_systems;
74 ++chemical_system_id)
75 {
76 kinetic_reactants->setChemicalSystemID(chemical_system_id);
77 Rxn_kinetics_map.emplace(chemical_system_id,
78 *kinetic_reactants->castToBaseClass());
79 }
80 // explicitly release and delete kinetic_reactants
81 kinetic_reactants.reset(nullptr);
82
83 use.Set_kinetics_in(true);
84 }
85
86 // rates
88
90
92
93 for (auto const& map_pair : process_id_to_component_name_map)
94 {
95 auto const transport_process_id = map_pair.first;
96 auto const& transport_process_variable = map_pair.second;
97
98 auto master_species =
99 master_bsearch(transport_process_variable.c_str());
100
101 _process_id_to_master_map[transport_process_id] = master_species;
102 }
103}
104
106 EquilibriumReactants const& equilibrium_reactants)
107{
108 // extract a part of function body from int
109 // Phreeqc::tidy_pp_assemblage(void)
110 count_elts = 0;
111 double coef = 1.0;
112 for (auto const& phase_component :
113 equilibrium_reactants.getPhaseComponents())
114 {
115 int phase_id;
116 struct phase* phase_component_ptr =
117 phase_bsearch(phase_component.first.c_str(), &phase_id, FALSE);
118 add_elt_list(phase_component_ptr->next_elt, coef);
119 }
120
121 cxxNameDouble nd = elt_list_NameDouble();
122 const_cast<cxxPPassemblage*>(equilibrium_reactants.castToBaseClass())
123 ->Set_eltList(nd);
124}
125
126void PhreeqcKernel::loadDatabase(std::string const& database)
127{
128 std::ifstream in(database);
129 if (!in)
130 {
131 OGS_FATAL("Unable to open database file '{:s}'.", database);
132 }
133 assert(phrq_io->get_istream() == nullptr);
134 phrq_io->push_istream(&in, false);
135 read_database();
136}
137
139{
140 std::vector<struct rate> rates(_reaction_rates.size());
141 int rate_id = 0;
142 for (auto const& reaction_rate : _reaction_rates)
143 {
144 rates[rate_id].name = reaction_rate.kinetic_reactant.data();
145
146 // Commands' strings are freed by Phreeqc dtor.
147 rates[rate_id].commands = static_cast<char*>(
148 malloc(sizeof(char) * reaction_rate.commands().size() + 1));
149 if (rates[rate_id].commands == nullptr)
150 {
151 OGS_FATAL("Could not allocate memory for rate[{:d}] commands.",
152 rate_id);
153 }
154 reaction_rate.commands().copy(rates[rate_id].commands,
155 std::string::npos);
156 rates[rate_id].commands[reaction_rate.commands().size()] = '\0';
157
158 rates[rate_id].new_def = 1;
159 rates[rate_id].linebase = nullptr;
160 rates[rate_id].varbase = nullptr;
161 rates[rate_id].loopbase = nullptr;
162
163 ++rate_id;
164 };
165}
166
168{
169 std::vector<GlobalVector*> process_solutions;
170
171 setAqueousSolutions(process_solutions);
172
173 setTimeStepSize(dt);
174
175 callPhreeqc(process_solutions);
176}
177
178static bool isHydrogen(std::string_view const element)
179{
180 return element == "H";
181}
182
184 std::vector<GlobalVector*> const& process_solutions)
185{
186 assert(!process_solutions.empty());
187 // Loop over chemical systems
188 std::size_t const num_chemical_systems = process_solutions[0]->size();
189 for (std::size_t chemical_system_id = 0;
190 chemical_system_id < num_chemical_systems;
191 ++chemical_system_id)
192 {
193 auto& aqueous_solution = Rxn_solution_map[chemical_system_id];
194
195 auto initial_aqueous_solution =
196 getOrCreateInitialAqueousSolution(aqueous_solution);
197
198 auto& components = initial_aqueous_solution->Get_comps();
199 // Loop over transport process id map to retrieve component
200 // concentrations from process solutions
201 for (auto const& map_pair : _process_id_to_master_map)
202 {
203 auto const transport_process_id = map_pair.first;
204 auto const& master_species = map_pair.second;
205
206 auto& transport_process_solution =
207 process_solutions[transport_process_id];
208
209 char const* const element_name = master_species->elt->name;
210 auto const concentration =
211 transport_process_solution->get(chemical_system_id);
212 if (isHydrogen(element_name))
213 {
214 // Set pH value by hydrogen concentration.
215 double const pH = -std::log10(concentration);
216 aqueous_solution.Set_ph(pH);
217
218 {
219 auto hydrogen = components.find("H(1)");
220 if (hydrogen != components.end())
221 {
222 hydrogen->second.Set_input_conc(pH);
223 }
224 }
225 }
226 else
227 {
228 // Set component concentrations.
229 components[element_name].Set_input_conc(concentration);
230 }
231 }
232 }
233}
234
236 cxxSolution& aqueous_solution)
237{
238 if (!aqueous_solution.Get_initial_data())
239 {
240 aqueous_solution.Set_initial_data(_initial_aqueous_solution.get());
241 aqueous_solution.Set_new_def(true);
242 }
243
244 return aqueous_solution.Get_initial_data();
245}
246
248{
249 // Loop over rxn kinetics map
250 for (auto& map_pair : Rxn_kinetics_map)
251 {
252 auto& kinetics = map_pair.second;
253 kinetics.Get_steps().push_back(dt);
254 }
255}
256
257void PhreeqcKernel::callPhreeqc(std::vector<GlobalVector*>& process_solutions)
258{
259 if (process_solutions.empty())
260 {
261 OGS_FATAL("About to access an empty process solutions vector.");
262 }
263 std::size_t const num_chemical_systems = process_solutions[0]->size();
264 for (std::size_t chemical_system_id = 0;
265 chemical_system_id < num_chemical_systems;
266 ++chemical_system_id)
267 {
268 Rxn_new_solution.insert(chemical_system_id);
269 new_solution = 1;
270 use.Set_n_solution_user(chemical_system_id);
271
272 if (!Rxn_pp_assemblage_map.empty())
273 {
274 Rxn_new_pp_assemblage.insert(chemical_system_id);
275 use.Set_pp_assemblage_in(true);
276 use.Set_n_pp_assemblage_user(chemical_system_id);
277 }
278
279 if (!Rxn_kinetics_map.empty())
280 {
281 use.Set_kinetics_in(true);
282 use.Set_n_kinetics_user(chemical_system_id);
283 }
284
285 initial_solutions(false);
286
287 reactions();
288
289 updateNodalProcessSolutions(process_solutions, chemical_system_id);
290
291 reset(chemical_system_id);
292 }
293}
294
295void PhreeqcKernel::reset(std::size_t const chemical_system_id)
296{
297 // Clean up
298 Rxn_new_solution.clear();
299
300 // Solution
301 {
302 Rxn_solution_map[chemical_system_id] = *_aqueous_solution;
303 Rxn_solution_map[chemical_system_id].Set_n_user_both(
304 chemical_system_id);
305 Rxn_solution_map[chemical_system_id].Set_pe(-s_eminus->la);
306 }
307
308 // Equilibrium reactants
309 if (!Rxn_pp_assemblage_map.empty())
310 {
311 Rxn_new_pp_assemblage.clear();
312 for (int j = 0; j < count_unknowns; j++)
313 {
314 if (x == nullptr || x[j]->type != PP)
315 continue;
316
317 auto& phase_components = Rxn_pp_assemblage_map[chemical_system_id]
318 .Get_pp_assemblage_comps();
319 phase_components[x[j]->pp_assemblage_comp_name].Set_moles(
320 x[j]->moles);
321 }
322 }
323
324 // Kinetics
325 if (!Rxn_kinetics_map.empty())
326 {
327 Rxn_kinetics_map[chemical_system_id].Get_steps().clear();
328 }
329}
330
332 std::vector<GlobalVector*> const& process_solutions,
333 std::size_t const node_id)
334{
335 for (auto const& map_pair : _process_id_to_master_map)
336 {
337 auto const transport_process_id = map_pair.first;
338 auto const& master_species = map_pair.second;
339
340 auto& transport_process_solution =
341 process_solutions[transport_process_id];
342
343 char const* const element_name = master_species->elt->name;
344 if (isHydrogen(element_name))
345 {
346 // Update hydrogen concentration by pH value.
347 auto const hydrogen_concentration = std::pow(10, s_hplus->la);
348 transport_process_solution->set(node_id, hydrogen_concentration);
349 }
350 else
351 {
352 // Update solutions of component transport processes.
353 auto const concentration =
354 master_species->primary
355 ? master_species->total_primary / mass_water_aq_x
356 : master_species->total / mass_water_aq_x;
357 transport_process_solution->set(node_id, concentration);
358 }
359 }
360}
361} // namespace PhreeqcKernelData
362} // namespace ChemistryLib
#define OGS_FATAL(...)
Definition Error.h:19
MathLib::EigenLisLinearSolver GlobalLinearSolver
ChemicalSolverInterface(MeshLib::Mesh const &mesh, GlobalLinearSolver &linear_solver_)
void setChemicalSystemID(std::size_t const chemical_system_id)
std::map< std::string, cxxPPassemblageComp > const & getPhaseComponents() const
std::vector< ReactionRate > const _reaction_rates
std::map< int, struct master * > _process_id_to_master_map
void setAqueousSolutions(std::vector< GlobalVector * > const &process_solutions)
void tidyEquilibriumReactants(EquilibriumReactants const &equilibrium_reactants)
void loadDatabase(std::string const &database)
void updateNodalProcessSolutions(std::vector< GlobalVector * > const &process_solutions, std::size_t const node_id)
PhreeqcKernel(MeshLib::Mesh const &mesh, GlobalLinearSolver &linear_solver, std::size_t const num_chemical_systems, std::vector< std::pair< int, std::string > > const &process_id_to_component_name_map, std::string const &database, AqueousSolution aqueous_solution, std::unique_ptr< EquilibriumReactants > &&equilibrium_reactants, std::unique_ptr< Kinetics > &&kinetic_reactants, std::vector< ReactionRate > &&reaction_rates)
std::unique_ptr< cxxSolution const > _aqueous_solution
cxxISolution * getOrCreateInitialAqueousSolution(cxxSolution &aqueous_solution)
std::unique_ptr< cxxISolution const > _initial_aqueous_solution
void callPhreeqc(std::vector< GlobalVector * > &process_solutions)
void executeSpeciationCalculation(double const dt) override
void reset(std::size_t const chemical_system_id)
static bool isHydrogen(std::string_view const element)