OGS
PhreeqcIO.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 "PhreeqcIO.h"
5
6#include <IPhreeqc.h>
7
8#include <boost/algorithm/string.hpp>
9#include <cmath>
10#include <fstream>
11#include <iomanip>
12#include <numeric>
13#include <sstream>
14
15#include "BaseLib/Algorithm.h"
20#include "MeshLib/Mesh.h"
23#include "PhreeqcIOData/Dump.h"
27#include "PhreeqcIOData/Knobs.h"
32
33namespace ChemistryLib
34{
36{
37namespace
38{
39template <class... Ts>
40struct overloaded : Ts...
41{
42 using Ts::operator()...;
43};
44template <class... Ts>
45overloaded(Ts...) -> overloaded<Ts...>;
46
47// Helper to iterate lines in a string_view without copying
49{
50public:
51 StringViewLineIterator(std::string_view text, std::size_t pos = 0)
52 : text_(text), pos_(pos)
53 {
54 }
55
56 bool getline(std::string_view& line)
57 {
58 if (pos_ >= text_.size())
59 {
60 return false;
61 }
62
63 if (const auto newline_pos = text_.find('\n', pos_);
64 newline_pos == std::string_view::npos)
65 {
66 line = text_.substr(pos_);
67 pos_ = text_.size();
68 }
69 else
70 {
71 line = text_.substr(pos_, newline_pos - pos_);
72 pos_ = newline_pos + 1;
73 }
74
75 // Remove trailing \r if present (Windows line endings)
76 if (!line.empty() && line.back() == '\r')
77 {
78 line.remove_suffix(1);
79 }
80
81 return true;
82 }
83
84 void skip(int num_lines)
85 {
86 for (int i = 0; i < num_lines && pos_ < text_.size(); ++i)
87 {
88 const auto newline_pos = text_.find('\n', pos_);
89 pos_ = (newline_pos == std::string_view::npos) ? text_.size()
90 : newline_pos + 1;
91 }
92 }
93
94private:
95 std::string_view text_;
96 std::size_t pos_;
97};
98
99std::vector<std::string> extractItemsFromLine(std::string const& line)
100{
101 std::vector<std::string> items;
102 std::string line_str(line);
103 boost::trim_if(line_str, boost::is_any_of("\t "));
104 boost::algorithm::split(items, line_str, boost::is_any_of("\t "),
105 boost::token_compress_on);
106 return items;
107}
108
109std::vector<double> parseAndFilterChemicalData(
110 std::string const& line,
111 std::vector<int> const& dropped_item_ids,
112 std::size_t chemical_system_id)
113{
114 std::vector<double> accepted_items;
115 std::vector<std::string> const items = extractItemsFromLine(line);
116 for (int item_id = 0; item_id < static_cast<int>(items.size()); ++item_id)
117 {
118 if (std::find(dropped_item_ids.begin(), dropped_item_ids.end(),
119 item_id) != dropped_item_ids.end())
120 {
121 continue;
122 }
123 double value;
124 try
125 {
126 value = std::stod(items[item_id]);
127 }
128 catch (const std::invalid_argument& e)
129 {
130 OGS_FATAL(
131 "Invalid argument. Could not convert string '{:s}' to "
132 "double for chemical system {:d}, column {:d}. "
133 "Exception '{:s}' was thrown.",
134 items[item_id], chemical_system_id + 1, item_id, e.what());
135 }
136 catch (const std::out_of_range& e)
137 {
138 OGS_FATAL(
139 "Out of range error. Could not convert string "
140 "'{:s}' to double for chemical system {:d}, column "
141 "{:d}. Exception '{:s}' was thrown.",
142 items[item_id], chemical_system_id + 1, item_id, e.what());
143 }
144 accepted_items.push_back(value);
145 }
146 return accepted_items;
147}
148
149template <typename DataBlock>
150std::ostream& operator<<(std::ostream& os,
151 std::vector<DataBlock> const& data_blocks)
152{
153 std::copy(data_blocks.begin(), data_blocks.end(),
154 std::ostream_iterator<DataBlock>(os));
155 return os;
156}
157
158void setAqueousSolution(std::vector<double> const& concentrations,
159 GlobalIndexType const& chemical_system_id,
160 AqueousSolution& aqueous_solution)
161{
162 GlobalIndexType const offset = aqueous_solution.pH->getRangeBegin();
163 GlobalIndexType const global_index = offset + chemical_system_id;
164
165 // components
166 auto& components = aqueous_solution.components;
167 for (unsigned component_id = 0; component_id < components.size();
168 ++component_id)
169 {
171 *components[component_id].amount);
172 components[component_id].amount->set(global_index,
173 concentrations[component_id]);
174 }
175
176 // pH
178 aqueous_solution.pH->set(global_index, concentrations.back());
179}
180
181template <typename Reactant>
182void initializeReactantMolality(Reactant& reactant,
183 GlobalIndexType const& chemical_system_id,
184 MaterialPropertyLib::Phase const& solid_phase,
185 MaterialPropertyLib::Phase const& liquid_phase,
186 MaterialPropertyLib::Medium const& medium,
188 double const t)
189{
190 auto const& solid_constituent = solid_phase.component(reactant.name);
191
192 if (solid_constituent.hasProperty(
194 {
195 auto const molality =
197 .template initialValue<double>(pos, t);
198
199 (*reactant.molality)[chemical_system_id] = molality;
200 (*reactant.molality_prev)[chemical_system_id] = molality;
201 }
202 else
203 {
204 auto const volume_fraction =
205 solid_constituent
207 .template initialValue<double>(pos, t);
208
209 (*reactant.volume_fraction)[chemical_system_id] = volume_fraction;
210
211 (*reactant.volume_fraction_prev)[chemical_system_id] = volume_fraction;
212
213 auto const fluid_density =
215 .template initialValue<double>(pos, t);
216
217 auto const porosity =
219 .template initialValue<double>(pos, t);
220
221 auto const molar_volume =
223 .template initialValue<double>(pos, t);
224
225 (*reactant.molality)[chemical_system_id] =
226 volume_fraction / fluid_density / porosity / molar_volume;
227
228 (*reactant.molality_prev)[chemical_system_id] =
229 (*reactant.molality)[chemical_system_id];
230 }
231}
232
233template <typename Reactant>
234void setReactantMolality(Reactant& reactant,
235 GlobalIndexType const& chemical_system_id,
236 MaterialPropertyLib::Phase const& solid_phase,
237 MaterialPropertyLib::Phase const& liquid_phase,
240 double const t, double const dt)
241{
242 auto const& solid_constituent = solid_phase.component(reactant.name);
243
244 if (solid_constituent.hasProperty(
246 {
247 (*reactant.molality_prev)[chemical_system_id] =
248 (*reactant.molality)[chemical_system_id];
249
250 return;
251 }
252
253 auto const volume_fraction =
254 (*reactant.volume_fraction)[chemical_system_id];
255
256 (*reactant.volume_fraction_prev)[chemical_system_id] =
257 (*reactant.volume_fraction)[chemical_system_id];
258
259 auto const fluid_density =
261 .template value<double>(vars, pos, t, dt);
262
263 auto const molar_volume =
265 .template value<double>(vars, pos, t, dt);
266
267 (*reactant.molality)[chemical_system_id] =
268 volume_fraction / fluid_density / vars.porosity / molar_volume;
269
270 (*reactant.molality_prev)[chemical_system_id] =
271 (*reactant.molality)[chemical_system_id];
272}
273
274template <typename Site>
276 GlobalIndexType const& chemical_system_id,
277 MaterialPropertyLib::Phase const& solid_phase,
279 double const t)
280{
281 auto const& solid_constituent = solid_phase.component(site.name);
282
283 auto const molality =
285 .template initialValue<double>(pos, t);
286
287 (*site.molality)[chemical_system_id] = molality;
288}
289
290template <typename Reactant>
291void updateReactantVolumeFraction(Reactant& reactant,
292 GlobalIndexType const& chemical_system_id,
293 MaterialPropertyLib::Medium const& medium,
295 double const porosity, double const t,
296 double const dt)
297{
298 auto const& solid_phase =
300 auto const& liquid_phase =
302
304
305 auto const liquid_density =
307 .template value<double>(vars, pos, t, dt);
308
309 auto const& solid_constituent = solid_phase.component(reactant.name);
310
311 if (solid_constituent.hasProperty(
313 {
314 return;
315 }
316
317 auto const molar_volume =
319 .template value<double>(vars, pos, t, dt);
320
321 (*reactant.volume_fraction)[chemical_system_id] +=
322 ((*reactant.molality)[chemical_system_id] -
323 (*reactant.molality_prev)[chemical_system_id]) *
324 liquid_density * porosity * molar_volume;
325}
326
327template <typename Reactant>
328void setPorosityPostReaction(Reactant& reactant,
329 GlobalIndexType const& chemical_system_id,
330 MaterialPropertyLib::Medium const& medium,
331 double& porosity)
332{
333 auto const& solid_phase =
335
336 auto const& solid_constituent = solid_phase.component(reactant.name);
337
338 if (solid_constituent.hasProperty(
340 {
341 return;
342 }
343
344 porosity -= ((*reactant.volume_fraction)[chemical_system_id] -
345 (*reactant.volume_fraction_prev)[chemical_system_id]);
346}
347
348template <typename Reactant>
350 Reactant const& reactant,
351 std::vector<GlobalIndexType> const& chemical_system_indices)
352{
353 double const sum = std::accumulate(
354 chemical_system_indices.begin(), chemical_system_indices.end(), 0.0,
355 [&](double const s, GlobalIndexType const id)
356 { return s + (*reactant.molality)[id]; });
357 return sum / chemical_system_indices.size();
358}
359} // namespace
360
361extern std::string specifyFileName(std::string const& project_file_name,
362 std::string const& file_extension);
363
366 std::string const& project_file_name,
367 std::string&& database,
368 std::unique_ptr<ChemicalSystem>&& chemical_system,
369 std::vector<ReactionRate>&& reaction_rates,
370 std::unique_ptr<UserPunch>&& user_punch,
371 std::unique_ptr<Output>&& output,
372 std::unique_ptr<Dump>&& dump,
373 Knobs&& knobs,
374 bool use_stream_mode)
376 _phreeqc_input_file(specifyFileName(project_file_name, ".inp")),
377 _database(std::move(database)),
378 _chemical_system(std::move(chemical_system)),
379 _reaction_rates(std::move(reaction_rates)),
380 _user_punch(std::move(user_punch)),
381 _output(std::move(output)),
382 _dump(std::move(dump)),
383 _knobs(std::move(knobs)),
384 _use_stream_mode(use_stream_mode)
385{
386 // initialize phreeqc instance
387 if (CreateIPhreeqc() != phreeqc_instance_id)
388 {
389 OGS_FATAL(
390 "Failed to initialize phreeqc instance, due to lack of memory.");
391 }
392
393 // load specified thermodynamic database
394 if (LoadDatabase(phreeqc_instance_id, _database.c_str()) != IPQ_OK)
395 {
396 OGS_FATAL(
397 "Failed in loading the specified thermodynamic database file: "
398 "{:s}.",
399 _database);
400 }
401
403 {
404 // In stream mode, disable file-based selected output
405 // Output will be retrieved via GetSelectedOutputString() from memory
406 // buffer
407 if (SetSelectedOutputFileOn(phreeqc_instance_id, 0) != IPQ_OK)
408 {
409 OGS_FATAL(
410 "Failed to disable file output for selected output in stream "
411 "mode.");
412 }
413
414 // Enable in-memory buffering of selected output
415 if (SetSelectedOutputStringOn(phreeqc_instance_id, 1) != IPQ_OK)
416 {
417 OGS_FATAL(
418 "Failed to enable string buffering for selected output in "
419 "stream mode.");
420 }
421
422 INFO(
423 "PhreeqcIO stream mode: selected output file disabled, using "
424 "internal memory buffer.");
425 }
426 else
427 {
428 // In file mode, enable file-based selected output (current behavior)
429 if (SetSelectedOutputFileOn(phreeqc_instance_id, 1) != IPQ_OK)
430 {
431 OGS_FATAL(
432 "Failed to fly the flag for the specified file {:s} where "
433 "phreeqc will write output.",
434 _output->basic_output_setups.output_file);
435 }
436 }
437
438 if (_dump)
439 {
441 {
442 // In stream mode, disable file-based dump output
443 // Dump data will be retrieved via GetDumpString() instead
444 INFO(
445 "PhreeqcIO is configured for stream-based data exchange: dump "
446 "file output disabled.");
447 SetDumpFileOn(phreeqc_instance_id, 0);
448
449 // Enable in-memory buffering of dump output
450 if (SetDumpStringOn(phreeqc_instance_id, 1) != IPQ_OK)
451 {
452 OGS_FATAL(
453 "Failed to enable string buffering for dump output in "
454 "stream mode.");
455 }
456 }
457 else
458 {
459 // In file mode, enable file-based dump output (current behavior)
460 // Chemical composition of the aqueous solution of last time step
461 // will be written into .dmp file
462 SetDumpFileOn(phreeqc_instance_id, 1);
463 }
464 }
465
467 {
468 INFO(
469 "PhreeqcIO is configured for stream-based data exchange: input "
470 "and output will be exchanged via in-memory strings.");
471 }
472}
473
475{
476 DestroyIPhreeqc(phreeqc_instance_id);
477}
478
480{
482
484
485 if (_user_punch)
486 {
488 }
489}
490
492 std::vector<double> const& concentrations,
493 GlobalIndexType const& chemical_system_id,
494 MaterialPropertyLib::Medium const& medium,
496 double const t)
497{
498 setAqueousSolution(concentrations, chemical_system_id,
499 *_chemical_system->aqueous_solution);
500
501 auto const& solid_phase =
503 auto const& liquid_phase =
505
506 for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
507 {
508 initializeReactantMolality(kinetic_reactant, chemical_system_id,
509 solid_phase, liquid_phase, medium, pos, t);
510 }
511
512 for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
513 {
514 initializeReactantMolality(equilibrium_reactant, chemical_system_id,
515 solid_phase, liquid_phase, medium, pos, t);
516 }
517
518 for (auto& exchanger : _chemical_system->exchangers)
519 {
520 initializeSiteMolality(exchanger, chemical_system_id, solid_phase, pos,
521 t);
522 }
523
524 for (auto& surface_site : _chemical_system->surface)
525 {
526 if (auto const surface_site_ptr =
527 std::get_if<MoleBasedSurfaceSite>(&surface_site))
528 {
529 initializeSiteMolality(*surface_site_ptr, chemical_system_id,
530 solid_phase, pos, t);
531 }
532 }
533}
534
536 std::vector<double> const& concentrations,
537 GlobalIndexType const& chemical_system_id,
538 MaterialPropertyLib::Medium const* medium,
540 ParameterLib::SpatialPosition const& pos, double const t, double const dt)
541{
542 setAqueousSolution(concentrations, chemical_system_id,
543 *_chemical_system->aqueous_solution);
544
545 auto const& solid_phase =
547 auto const& liquid_phase =
549
550 for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
551 {
552 setReactantMolality(kinetic_reactant, chemical_system_id, solid_phase,
553 liquid_phase, vars, pos, t, dt);
554 }
555
556 for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
557 {
558 setReactantMolality(equilibrium_reactant, chemical_system_id,
559 solid_phase, liquid_phase, vars, pos, t, dt);
560 }
561}
562
564{
566 {
567 // Stream-based data exchange
568 DBUG("Executing speciation with stream-based data exchange.");
569
570 // This gives us a null-terminated string required by RunString().
571 std::string input_content = writeInputsToStringStream(dt).str();
572
573 // Execute PhreeqC with string input
574 callPhreeqcWithString(input_content);
575
576 // Retrieve output from memory buffer and parse
577 auto output_content = retrieveSelectedOutputString();
578 readOutputsFromStringView(output_content);
579
580 // Handle dump data if surfaces/exchangers exist
581 if (_dump)
582 {
583 auto dump_content = retrieveDumpString();
585 }
586 }
587 else
588 {
589 // File-based data exchange (original behavior)
590 DBUG("Executing speciation with file-based data exchange.");
591
593
594 callPhreeqc();
595
597 }
598}
599
601 int const component_id, GlobalIndexType const chemical_system_id) const
602{
603 auto const& aqueous_solution = *_chemical_system->aqueous_solution;
604 auto& components = aqueous_solution.components;
605 auto const& pH = *aqueous_solution.pH;
606
607 if (component_id < static_cast<int>(components.size()))
608 {
610 *components[component_id].amount);
611
612 return components[component_id].amount->get(chemical_system_id);
613 }
614
615 // pH
616 MathLib::LinAlg::setLocalAccessibleVector(*aqueous_solution.pH);
617
618 return pH.get(chemical_system_id);
619}
620
622{
623 if (!_dump)
624 {
625 return;
626 }
627
628 auto const& dump_file = _dump->dump_file;
629 std::ifstream in(dump_file);
630 if (!in)
631 {
632 // return if phreeqc dump file doesn't exist. This happens in
633 // the first time step when no dump file is provided by the user.
634 return;
635 }
636
637 _dump->readDumpFile(in, _num_chemical_systems);
638
639 if (!in)
640 {
641 OGS_FATAL("Error when reading phreeqc dump file '{:s}'", dump_file);
642 }
643
644 in.close();
645}
646
648 std::string_view dump_content)
649{
650 if (!_dump)
651 {
652 return;
653 }
654
655 if (dump_content.empty())
656 {
657 // return if dump content is empty. This happens in
658 // the first time step when no dump data is available.
659 DBUG(
660 "Dump content is empty, skipping aqueous solutions initialization "
661 "from dump.");
662 return;
663 }
664
665 _dump->readDumpFromString(dump_content, _num_chemical_systems);
666}
667
668std::stringstream PhreeqcIO::writeInputsToStringStream(double const dt)
669{
670 DBUG("Serializing phreeqc inputs to string stream.");
671 std::stringstream ss;
672
673 ss << std::scientific
674 << std::setprecision(std::numeric_limits<double>::max_digits10);
675 *this << dt;
676 ss << *this;
677
678 if (!ss)
679 {
680 OGS_FATAL("Failed in serializing phreeqc inputs to string.");
681 }
682
683 return ss;
684}
685
686void PhreeqcIO::writeInputsToFile(double const dt)
687{
688 DBUG("Writing phreeqc inputs into file '{:s}'.", _phreeqc_input_file);
689 std::ofstream out(_phreeqc_input_file, std::ofstream::out);
690
691 if (!out)
692 {
693 OGS_FATAL("Could not open file '{:s}' for writing phreeqc inputs.",
695 }
696
697 out << std::scientific
698 << std::setprecision(std::numeric_limits<double>::max_digits10);
699 *this << dt;
700 out << *this;
701
702 if (!out)
703 {
704 OGS_FATAL("Failed in generating phreeqc input file '{:s}'.",
706 }
707
708 out.close();
709}
710
711std::ostream& operator<<(std::ostream& os, PhreeqcIO const& phreeqc_io)
712{
713 bool const fixing_pe =
714 phreeqc_io._chemical_system->aqueous_solution->fixing_pe;
715 if (fixing_pe)
716 {
717 os << "PHASES\n"
718 << "Fix_pe\n"
719 << "e- = e-\n"
720 << "log_k 0.0\n\n";
721 }
722
723 os << phreeqc_io._knobs << "\n";
724
725 os << *phreeqc_io._output << "\n";
726
727 auto const& user_punch = phreeqc_io._user_punch;
728 if (user_punch)
729 {
730 os << *user_punch << "\n";
731 }
732
733 auto const& reaction_rates = phreeqc_io._reaction_rates;
734 if (!reaction_rates.empty())
735 {
736 os << "RATES\n";
737 os << reaction_rates << "\n";
738 }
739
740 for (std::size_t chemical_system_id = 0;
741 chemical_system_id < phreeqc_io._num_chemical_systems;
742 ++chemical_system_id)
743 {
744 os << "SOLUTION " << chemical_system_id + 1 << "\n";
745 phreeqc_io._chemical_system->aqueous_solution->print(
746 os, chemical_system_id);
747
748 auto const& dump = phreeqc_io._dump;
749 if (dump)
750 {
751 auto const& aqueous_solutions_prev = dump->aqueous_solutions_prev;
752 // Print previous aqueous solution if it exists.
753 if (!aqueous_solutions_prev.empty())
754 {
755 os << aqueous_solutions_prev[chemical_system_id] << "\n\n";
756 }
757 }
758
759 os << "USE solution none\n";
760 os << "END\n\n";
761
762 os << "USE solution " << chemical_system_id + 1 << "\n\n";
763
764 auto const& equilibrium_reactants =
765 phreeqc_io._chemical_system->equilibrium_reactants;
766 if (!equilibrium_reactants.empty() || fixing_pe)
767 {
768 os << "EQUILIBRIUM_PHASES " << chemical_system_id + 1 << "\n";
769 for (auto const& equilibrium_reactant : equilibrium_reactants)
770 {
771 equilibrium_reactant.print(os, chemical_system_id);
772 }
773 fixing_pe
774 ? os << "Fix_pe "
775 << -phreeqc_io._chemical_system->aqueous_solution->pe0
776 << " O2(g)\n\n"
777 : os << "\n";
778 }
779
780 auto const& kinetic_reactants =
781 phreeqc_io._chemical_system->kinetic_reactants;
782 if (!kinetic_reactants.empty())
783 {
784 os << "KINETICS " << chemical_system_id + 1 << "\n";
785 for (auto const& kinetic_reactant : kinetic_reactants)
786 {
787 kinetic_reactant.print(os, chemical_system_id);
788 }
789 os << "-steps " << phreeqc_io._dt << "\n\n";
790 }
791
792 auto const& surface = phreeqc_io._chemical_system->surface;
793 if (!surface.empty())
794 {
795 // To get the amount of surface species from the previous time step,
796 // an equilibration calculation with the previous aqueous solution
797 // is needed. The previous aqueous solution is saved using the
798 // PHREEQC keyword "DUMP" in the dump file and then stored as
799 // SOLUTION_RAW within aqueous_solutions_prev. Along with the
800 // PHREEQC keyword 'SURFACE', distinguish between the current and
801 // previous solutions, the previous solution number is added to the
802 // total number of chemical systems (_num_chemical_systems).
803 os << "SURFACE " << chemical_system_id + 1 << "\n";
804 std::size_t aqueous_solution_id =
805 dump->aqueous_solutions_prev.empty()
806 ? chemical_system_id + 1
807 : phreeqc_io._num_chemical_systems + chemical_system_id + 1;
808 os << "-equilibrate with solution " << aqueous_solution_id << "\n";
809
810 // print unit
811 if (std::holds_alternative<DensityBasedSurfaceSite>(
812 surface.front()))
813 {
814 os << "-sites_units density\n";
815 }
816 else
817 {
818 os << "-sites_units absolute\n";
819 }
820
821 for (auto const& surface_site : surface)
822 {
823 std::visit(
824 overloaded{[&os](DensityBasedSurfaceSite const& s)
825 {
826 os << s.name << " " << s.site_density << " "
827 << s.specific_surface_area << " "
828 << s.mass << "\n";
829 },
830 [&os, chemical_system_id](
831 MoleBasedSurfaceSite const& s) {
832 os << s.name << " "
833 << (*s.molality)[chemical_system_id]
834 << "\n";
835 }},
836 surface_site);
837 }
838
839 // overlook the effect of the buildup of charges onto the surface
840 if (std::holds_alternative<MoleBasedSurfaceSite>(surface.front()))
841 {
842 os << "-no_edl\n";
843 }
844 // Here the PHREEQC "save" command is printed to save the solution
845 // internally in PHREEQC. The solution composition is saved after
846 // equilibration with the surface has been completed. The solution
847 // will not be saved for use in the next PHREEQC run.
848 os << "SAVE solution " << chemical_system_id + 1 << "\n";
849 }
850
851 auto const& exchangers = phreeqc_io._chemical_system->exchangers;
852 if (!exchangers.empty())
853 {
854 os << "EXCHANGE " << chemical_system_id + 1 << "\n";
855 std::size_t const aqueous_solution_id =
856 dump->aqueous_solutions_prev.empty()
857 ? chemical_system_id + 1
858 : phreeqc_io._num_chemical_systems + chemical_system_id + 1;
859 os << "-equilibrate with solution " << aqueous_solution_id << "\n";
860 for (auto const& exchanger : exchangers)
861 {
862 exchanger.print(os, chemical_system_id);
863 }
864 os << "SAVE solution " << chemical_system_id + 1 << "\n";
865 }
866
867 os << "END\n\n";
868 }
869
870 auto const& dump = phreeqc_io._dump;
871 if (dump)
872 {
873 dump->print(os, phreeqc_io._num_chemical_systems);
874 }
875
876 return os;
877}
878
880{
881 INFO("Phreeqc: Executing chemical calculation.");
882 if (RunFile(phreeqc_instance_id, _phreeqc_input_file.c_str()) != IPQ_OK)
883 {
884 OutputErrorString(phreeqc_instance_id);
885 OGS_FATAL(
886 "Failed in performing speciation calculation with the generated "
887 "phreeqc input file '{:s}'.",
889 }
890}
891
892void PhreeqcIO::callPhreeqcWithString(std::string const& input_content) const
893{
894 INFO("Phreeqc: Executing chemical calculation from string stream.");
895
896 if (RunString(phreeqc_instance_id, input_content.c_str()) != IPQ_OK)
897 {
898 OutputErrorString(phreeqc_instance_id);
899 OGS_FATAL(
900 "Failed in performing speciation calculation with string input.");
901 }
902}
903
905{
906 DBUG("Retrieving phreeqc results from internal memory buffer.");
907
908 const char* output_ptr = GetSelectedOutputString(phreeqc_instance_id);
909 if (!output_ptr)
910 {
911 DBUG("Selected output string is empty or unavailable.");
912 return std::string_view();
913 }
914
915 return std::string_view(output_ptr);
916}
917
918std::string_view PhreeqcIO::retrieveDumpString() const
919{
920 DBUG("Retrieving phreeqc dump data from internal buffer.");
921
922 const char* dump_ptr = GetDumpString(phreeqc_instance_id);
923 if (!dump_ptr)
924 {
925 // Dump might be empty in first time step
926 DBUG("Dump string is empty or unavailable.");
927 return std::string_view();
928 }
929
930 return std::string_view(dump_ptr);
931}
932
934{
935 auto const& basic_output_setups = _output->basic_output_setups;
936 auto const& phreeqc_result_file = basic_output_setups.output_file;
937 DBUG("Reading phreeqc results from file '{:s}'.", phreeqc_result_file);
938 std::ifstream in(phreeqc_result_file);
939
940 if (!in)
941 {
942 OGS_FATAL("Could not open phreeqc result file '{:s}'.",
943 phreeqc_result_file);
944 }
945
946 in >> *this;
947
948 if (!in)
949 {
950 OGS_FATAL("Error when reading phreeqc result file '{:s}'",
951 phreeqc_result_file);
952 }
953
954 in.close();
955}
956
957void PhreeqcIO::readOutputsFromStringView(std::string_view output_content)
958{
959 DBUG("Reading phreeqc results from string buffer.");
960
961 if (output_content.empty())
962 {
963 OGS_FATAL("Output content is empty.");
964 }
965
966 StringViewLineIterator line_iter(output_content);
967 std::string_view line;
968
969 // Skip the headline
970 line_iter.getline(line);
971
972 if (!_output)
973 {
974 OGS_FATAL(
975 "No output configuration available when reading PhreeqC results "
976 "strings.");
977 }
978
979 auto const& output = *_output;
980 auto const& dropped_item_ids = output.dropped_item_ids;
981
982 auto const& surface = _chemical_system->surface;
983 auto const& exchangers = _chemical_system->exchangers;
984
985 int const num_skipped_lines =
986 1 + (!surface.empty() ? 1 : 0) + (!exchangers.empty() ? 1 : 0);
987
988 auto& equilibrium_reactants = _chemical_system->equilibrium_reactants;
989 auto& kinetic_reactants = _chemical_system->kinetic_reactants;
990
991 for (std::size_t chemical_system_id = 0;
992 chemical_system_id < _num_chemical_systems;
993 ++chemical_system_id)
994 {
995 // Skip equilibrium calculation result of initial solution
996 line_iter.skip(num_skipped_lines);
997
998 // Get calculation result of the solution after the reaction
999 if (!line_iter.getline(line))
1000 {
1001 OGS_FATAL(
1002 "Error when reading calculation result of Solution {:d} "
1003 "after the reaction.",
1004 chemical_system_id);
1005 }
1006
1007 auto accepted_items = parseAndFilterChemicalData(
1008 std::string(line), dropped_item_ids, chemical_system_id);
1009 assert(accepted_items.size() == output.accepted_items.size());
1010
1011 auto& aqueous_solution = _chemical_system->aqueous_solution;
1012 auto& components = aqueous_solution->components;
1013 auto& user_punch = _user_punch;
1014
1015 GlobalIndexType const offset = aqueous_solution->pH->getRangeBegin();
1016 GlobalIndexType const global_index = offset + chemical_system_id;
1017
1018 for (int item_id = 0; item_id < static_cast<int>(accepted_items.size());
1019 ++item_id)
1020 {
1021 auto const& accepted_item = output.accepted_items[item_id];
1022 auto const& item_name = accepted_item.name;
1023
1024 auto compare_by_name = [&item_name](auto const& item)
1025 { return item.name == item_name; };
1026
1027 switch (accepted_item.item_type)
1028 {
1029 case ItemType::pH:
1030 {
1031 // Update pH value
1033 *aqueous_solution->pH);
1034 aqueous_solution->pH->set(
1035 global_index, std::pow(10, -accepted_items[item_id]));
1036 break;
1037 }
1038 case ItemType::pe:
1039 {
1040 // Update pe value
1041 (*aqueous_solution->pe)[chemical_system_id] =
1042 accepted_items[item_id];
1043 break;
1044 }
1046 {
1047 // Update component concentrations
1048 auto const& component = BaseLib::findElementOrError(
1049 components, compare_by_name,
1050 [&]() {
1051 OGS_FATAL("Could not find component '{:s}'.",
1052 item_name);
1053 });
1055 *component.amount);
1056 component.amount->set(global_index,
1057 accepted_items[item_id]);
1058 break;
1059 }
1061 {
1062 // Update amounts of equilibrium reactant
1063 auto const& equilibrium_reactant =
1065 equilibrium_reactants, compare_by_name,
1066 [&]()
1067 {
1068 OGS_FATAL(
1069 "Could not find equilibrium reactant "
1070 "'{:s}'",
1071 item_name);
1072 });
1073 (*equilibrium_reactant.molality)[chemical_system_id] =
1074 accepted_items[item_id];
1075 break;
1076 }
1078 {
1079 // Update amounts of kinetic reactants
1080 auto const& kinetic_reactant = BaseLib::findElementOrError(
1081 kinetic_reactants, compare_by_name,
1082 [&]() {
1083 OGS_FATAL("Could not find kinetic reactant '{:s}'.",
1084 item_name);
1085 });
1086 (*kinetic_reactant.molality)[chemical_system_id] =
1087 accepted_items[item_id];
1088 break;
1089 }
1091 {
1092 assert(user_punch);
1093 auto const& secondary_variables =
1094 user_punch->secondary_variables;
1095 // Update values of secondary variables
1096 auto const& secondary_variable =
1098 secondary_variables, compare_by_name,
1099 [&]() {
1100 OGS_FATAL(
1101 "Could not find secondary variable '{:s}'.",
1102 item_name);
1103 });
1104 (*secondary_variable.value)[chemical_system_id] =
1105 accepted_items[item_id];
1106 break;
1107 }
1108 }
1109 }
1110 }
1111}
1112
1113std::istream& operator>>(std::istream& in, PhreeqcIO& phreeqc_io)
1114{
1115 // Skip the headline
1116 in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
1117
1118 std::string line;
1119 auto const& output = *phreeqc_io._output;
1120 auto const& dropped_item_ids = output.dropped_item_ids;
1121
1122 auto const& surface = phreeqc_io._chemical_system->surface;
1123 auto const& exchangers = phreeqc_io._chemical_system->exchangers;
1124
1125 int const num_skipped_lines =
1126 1 + (!surface.empty() ? 1 : 0) + (!exchangers.empty() ? 1 : 0);
1127
1128 auto& equilibrium_reactants =
1129 phreeqc_io._chemical_system->equilibrium_reactants;
1130 auto& kinetic_reactants = phreeqc_io._chemical_system->kinetic_reactants;
1131
1132 for (std::size_t chemical_system_id = 0;
1133 chemical_system_id < phreeqc_io._num_chemical_systems;
1134 ++chemical_system_id)
1135 {
1136 // Skip equilibrium calculation result of initial solution
1137 for (int i = 0; i < num_skipped_lines; ++i)
1138 {
1139 in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
1140 }
1141
1142 // Get calculation result of the solution after the reaction
1143 if (!std::getline(in, line))
1144 {
1145 OGS_FATAL(
1146 "Error when reading calculation result of Solution {:d} "
1147 "after the reaction.",
1148 chemical_system_id);
1149 }
1150
1151 auto accepted_items = parseAndFilterChemicalData(line, dropped_item_ids,
1152 chemical_system_id);
1153 assert(accepted_items.size() == output.accepted_items.size());
1154
1155 auto& aqueous_solution = phreeqc_io._chemical_system->aqueous_solution;
1156 auto& components = aqueous_solution->components;
1157 auto& user_punch = phreeqc_io._user_punch;
1158
1159 GlobalIndexType const offset = aqueous_solution->pH->getRangeBegin();
1160 GlobalIndexType const global_index = offset + chemical_system_id;
1161
1162 for (int item_id = 0; item_id < static_cast<int>(accepted_items.size());
1163 ++item_id)
1164 {
1165 auto const& accepted_item = output.accepted_items[item_id];
1166 auto const& item_name = accepted_item.name;
1167
1168 auto compare_by_name = [&item_name](auto const& item)
1169 { return item.name == item_name; };
1170
1171 switch (accepted_item.item_type)
1172 {
1173 case ItemType::pH:
1174 {
1175 // Update pH value
1177 *aqueous_solution->pH);
1178 aqueous_solution->pH->set(
1179 global_index, std::pow(10, -accepted_items[item_id]));
1180 break;
1181 }
1182 case ItemType::pe:
1183 {
1184 // Update pe value
1185 (*aqueous_solution->pe)[chemical_system_id] =
1186 accepted_items[item_id];
1187 break;
1188 }
1190 {
1191 // Update component concentrations
1192 auto const& component = BaseLib::findElementOrError(
1193 components, compare_by_name,
1194 [&]() {
1195 OGS_FATAL("Could not find component '{:s}'.",
1196 item_name);
1197 });
1199 *component.amount);
1200 component.amount->set(global_index,
1201 accepted_items[item_id]);
1202 break;
1203 }
1205 {
1206 // Update amounts of equilibrium reactant
1207 auto const& equilibrium_reactant =
1209 equilibrium_reactants, compare_by_name,
1210 [&]()
1211 {
1212 OGS_FATAL(
1213 "Could not find equilibrium reactant "
1214 "'{:s}'",
1215 item_name);
1216 });
1217 (*equilibrium_reactant.molality)[chemical_system_id] =
1218 accepted_items[item_id];
1219 break;
1220 }
1222 {
1223 // Update amounts of kinetic reactants
1224 auto const& kinetic_reactant = BaseLib::findElementOrError(
1225 kinetic_reactants, compare_by_name,
1226 [&]() {
1227 OGS_FATAL("Could not find kinetic reactant '{:s}'.",
1228 item_name);
1229 });
1230 (*kinetic_reactant.molality)[chemical_system_id] =
1231 accepted_items[item_id];
1232 break;
1233 }
1235 {
1236 assert(user_punch);
1237 auto const& secondary_variables =
1238 user_punch->secondary_variables;
1239 // Update values of secondary variables
1240 auto const& secondary_variable =
1242 secondary_variables, compare_by_name,
1243 [&]() {
1244 OGS_FATAL(
1245 "Could not find secondary variable '{:s}'.",
1246 item_name);
1247 });
1248 (*secondary_variable.value)[chemical_system_id] =
1249 accepted_items[item_id];
1250 break;
1251 }
1252 }
1253 }
1254 }
1255
1256 return in;
1257}
1258
1259std::vector<std::string> const PhreeqcIO::getComponentList() const
1260{
1261 std::vector<std::string> component_names;
1262 auto const& components = _chemical_system->aqueous_solution->components;
1263 std::transform(components.begin(), components.end(),
1264 std::back_inserter(component_names),
1265 [](auto const& c) { return c.name; });
1266
1267 component_names.push_back("H");
1268
1269 return component_names;
1270}
1271
1273 GlobalIndexType const& chemical_system_id,
1274 MaterialPropertyLib::Medium const& medium,
1275 ParameterLib::SpatialPosition const& pos, double const porosity,
1276 double const t, double const dt)
1277{
1278 for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
1279 {
1280 updateReactantVolumeFraction(kinetic_reactant, chemical_system_id,
1281 medium, pos, porosity, t, dt);
1282 }
1283
1284 for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
1285 {
1286 updateReactantVolumeFraction(equilibrium_reactant, chemical_system_id,
1287 medium, pos, porosity, t, dt);
1288 }
1289}
1290
1292 GlobalIndexType const& chemical_system_id,
1293 MaterialPropertyLib::Medium const& medium,
1294 double& porosity)
1295{
1296 for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
1297 {
1298 setPorosityPostReaction(kinetic_reactant, chemical_system_id, medium,
1299 porosity);
1300 }
1301
1302 for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
1303 {
1304 setPorosityPostReaction(equilibrium_reactant, chemical_system_id,
1305 medium, porosity);
1306 }
1307}
1308
1310 std::size_t const ele_id,
1311 std::vector<GlobalIndexType> const& chemical_system_indices)
1312{
1313 for (auto const& kinetic_reactant : _chemical_system->kinetic_reactants)
1314 {
1315 (*kinetic_reactant.mesh_prop_molality)[ele_id] =
1316 averageReactantMolality(kinetic_reactant, chemical_system_indices);
1317 }
1318
1319 for (auto const& equilibrium_reactant :
1320 _chemical_system->equilibrium_reactants)
1321 {
1322 (*equilibrium_reactant.mesh_prop_molality)[ele_id] =
1323 averageReactantMolality(equilibrium_reactant,
1324 chemical_system_indices);
1325 }
1326}
1327} // namespace PhreeqcIOData
1328} // namespace ChemistryLib
Definition of one reactive chemical system for PHREEQC coupling.
#define OGS_FATAL(...)
Definition Error.h:19
MathLib::EigenLisLinearSolver GlobalLinearSolver
GlobalMatrix::IndexType GlobalIndexType
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:28
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
Per-system aqueous state exchanged with PHREEQC.
PHREEQC-backed ChemicalSolverInterface implementation.
std::vector< GlobalIndexType > chemical_system_index_map
ChemicalSolverInterface(MeshLib::Mesh const &mesh, GlobalLinearSolver &linear_solver_)
double getConcentration(int const component_id, GlobalIndexType const chemical_system_id) const override
void setAqueousSolutionsPrevFromDumpFile() override
PhreeqcIO(MeshLib::Mesh const &mesh, GlobalLinearSolver &linear_solver, std::string const &project_file_name, std::string &&database, std::unique_ptr< ChemicalSystem > &&chemical_system, std::vector< ReactionRate > &&reaction_rates, std::unique_ptr< UserPunch > &&user_punch, std::unique_ptr< Output > &&output, std::unique_ptr< Dump > &&dump, Knobs &&knobs, bool use_stream_mode)
std::unique_ptr< ChemicalSystem > _chemical_system
Definition PhreeqcIO.h:201
std::vector< ReactionRate > const _reaction_rates
Definition PhreeqcIO.h:202
std::unique_ptr< UserPunch > _user_punch
Definition PhreeqcIO.h:203
void writeInputsToFile(double const dt)
void setChemicalSystemConcrete(std::vector< double > const &concentrations, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const *medium, MaterialPropertyLib::VariableArray const &vars, ParameterLib::SpatialPosition const &pos, double const t, double const dt) override
std::unique_ptr< Dump > const _dump
Definition PhreeqcIO.h:205
std::unique_ptr< Output > const _output
Definition PhreeqcIO.h:204
void readOutputsFromStringView(std::string_view output_content)
void callPhreeqcWithString(std::string const &input_content) const
std::string_view retrieveDumpString() const
void computeSecondaryVariable(std::size_t const ele_id, std::vector< GlobalIndexType > const &chemical_system_indices) override
void initializeChemicalSystemConcrete(std::vector< double > const &concentrations, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const &medium, ParameterLib::SpatialPosition const &pos, double const t) override
void updateVolumeFractionPostReaction(GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const &medium, ParameterLib::SpatialPosition const &pos, double const porosity, double const t, double const dt) override
std::string_view retrieveSelectedOutputString() const
void executeSpeciationCalculation(double const dt) override
void setAqueousSolutionsPrevFromDumpString(std::string_view dump_content)
std::vector< std::string > const getComponentList() const override
std::stringstream writeInputsToStringStream(double const dt)
void updatePorosityPostReaction(GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const &medium, double &porosity) override
Phase const & phase(std::size_t index) const
Definition Medium.cpp:24
Component const & component(std::size_t const &index) const
Definition Phase.cpp:61
ranges::range_reference_t< Range > findElementOrError(Range &range, std::predicate< ranges::range_reference_t< Range > > auto &&predicate, std::invocable auto error_callback)
Definition Algorithm.h:74
std::vector< double > parseAndFilterChemicalData(std::string const &line, std::vector< int > const &dropped_item_ids, std::size_t chemical_system_id)
void initializeSiteMolality(Site &site, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Phase const &solid_phase, ParameterLib::SpatialPosition const &pos, double const t)
void initializeReactantMolality(Reactant &reactant, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Phase const &solid_phase, MaterialPropertyLib::Phase const &liquid_phase, MaterialPropertyLib::Medium const &medium, ParameterLib::SpatialPosition const &pos, double const t)
std::vector< std::string > extractItemsFromLine(std::string const &line)
Definition PhreeqcIO.cpp:99
void setPorosityPostReaction(Reactant &reactant, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const &medium, double &porosity)
void setReactantMolality(Reactant &reactant, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Phase const &solid_phase, MaterialPropertyLib::Phase const &liquid_phase, MaterialPropertyLib::VariableArray const &vars, ParameterLib::SpatialPosition const &pos, double const t, double const dt)
void setAqueousSolution(std::vector< double > const &concentrations, GlobalIndexType const &chemical_system_id, AqueousSolution &aqueous_solution)
static double averageReactantMolality(Reactant const &reactant, std::vector< GlobalIndexType > const &chemical_system_indices)
void updateReactantVolumeFraction(Reactant &reactant, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const &medium, ParameterLib::SpatialPosition const &pos, double const porosity, double const t, double const dt)
std::ostream & operator<<(std::ostream &os, PhreeqcIO const &phreeqc_io)
std::string specifyFileName(std::string const &project_file_name, std::string const &file_extension)
std::istream & operator>>(std::istream &in, PhreeqcIO &phreeqc_io)
void setLocalAccessibleVector(PETScVector const &x)
Definition LinAlg.cpp:20