12#include <boost/algorithm/string.hpp>
47 using Ts::operator()...;
69 if (
const auto newline_pos =
text_.find(
'\n',
pos_);
70 newline_pos == std::string_view::npos)
78 pos_ = newline_pos + 1;
82 if (!line.empty() && line.back() ==
'\r')
84 line.remove_suffix(1);
90 void skip(
int const num_lines)
92 for (
int i = 0; i < num_lines &&
pos_ <
text_.size(); ++i)
94 const auto newline_pos =
text_.find(
'\n',
pos_);
95 pos_ = (newline_pos == std::string_view::npos) ?
text_.size()
107 std::vector<std::string> items;
108 std::string line_str(line);
109 boost::trim_if(line_str, boost::is_any_of(
"\t "));
110 boost::algorithm::split(items, line_str, boost::is_any_of(
"\t "),
111 boost::token_compress_on);
116 std::string_view
const line,
117 std::vector<int>
const& dropped_item_ids,
118 std::size_t
const chemical_system_id)
120 std::vector<double> accepted_items;
122 for (
int item_id = 0; item_id < static_cast<int>(items.size()); ++item_id)
124 if (std::find(dropped_item_ids.begin(), dropped_item_ids.end(),
125 item_id) != dropped_item_ids.end())
132 value = std::stod(items[item_id]);
134 catch (
const std::invalid_argument& e)
137 "Invalid argument. Could not convert string '{:s}' to "
138 "double for chemical system {:d}, column {:d}. "
139 "Exception '{:s}' was thrown.",
140 items[item_id], chemical_system_id + 1, item_id, e.what());
142 catch (
const std::out_of_range& e)
145 "Out of range error. Could not convert string "
146 "'{:s}' to double for chemical system {:d}, column "
147 "{:d}. Exception '{:s}' was thrown.",
148 items[item_id], chemical_system_id + 1, item_id, e.what());
150 accepted_items.push_back(value);
152 return accepted_items;
155template <
typename DataBlock>
157 std::vector<DataBlock>
const& data_blocks)
159 std::copy(data_blocks.begin(), data_blocks.end(),
160 std::ostream_iterator<DataBlock>(os));
164template <
typename Reactant>
173 auto const& solid_constituent = solid_phase.
component(reactant.name);
175 if (solid_constituent.hasProperty(
178 auto const molality =
180 .template initialValue<double>(pos, t);
182 (*reactant.molality)[chemical_system_id] = molality;
183 (*reactant.molality_prev)[chemical_system_id] = molality;
187 auto const volume_fraction =
190 .template initialValue<double>(pos, t);
192 (*reactant.volume_fraction)[chemical_system_id] = volume_fraction;
194 (*reactant.volume_fraction_prev)[chemical_system_id] = volume_fraction;
196 auto const fluid_density =
198 .template initialValue<double>(pos, t);
200 auto const porosity =
202 .template initialValue<double>(pos, t);
204 auto const molar_volume =
206 .template initialValue<double>(pos, t);
208 (*reactant.molality)[chemical_system_id] =
209 volume_fraction / fluid_density / porosity / molar_volume;
211 (*reactant.molality_prev)[chemical_system_id] =
212 (*reactant.molality)[chemical_system_id];
216template <
typename Reactant>
223 double const t,
double const dt)
225 auto const& solid_constituent = solid_phase.
component(reactant.name);
227 if (solid_constituent.hasProperty(
230 (*reactant.molality_prev)[chemical_system_id] =
231 (*reactant.molality)[chemical_system_id];
236 auto const volume_fraction =
237 (*reactant.volume_fraction)[chemical_system_id];
239 (*reactant.volume_fraction_prev)[chemical_system_id] =
240 (*reactant.volume_fraction)[chemical_system_id];
242 auto const fluid_density =
244 .template value<double>(vars, pos, t, dt);
246 auto const molar_volume =
248 .template value<double>(vars, pos, t, dt);
250 (*reactant.molality)[chemical_system_id] =
251 volume_fraction / fluid_density / vars.
porosity / molar_volume;
253 (*reactant.molality_prev)[chemical_system_id] =
254 (*reactant.molality)[chemical_system_id];
257template <
typename Site>
264 auto const& solid_constituent = solid_phase.
component(site.name);
266 auto const molality =
268 .template initialValue<double>(pos, t);
270 (*site.molality)[chemical_system_id] = molality;
273template <
typename Reactant>
278 double const porosity,
double const t,
281 auto const& solid_phase =
283 auto const& liquid_phase =
288 auto const liquid_density =
290 .template value<double>(vars, pos, t, dt);
292 auto const& solid_constituent = solid_phase.component(reactant.name);
294 if (solid_constituent.hasProperty(
300 auto const molar_volume =
302 .template value<double>(vars, pos, t, dt);
304 (*reactant.volume_fraction)[chemical_system_id] +=
305 ((*reactant.molality)[chemical_system_id] -
306 (*reactant.molality_prev)[chemical_system_id]) *
307 liquid_density * porosity * molar_volume;
310template <
typename Reactant>
316 auto const& solid_phase =
319 auto const& solid_constituent = solid_phase.
component(reactant.name);
321 if (solid_constituent.hasProperty(
327 porosity -= ((*reactant.volume_fraction)[chemical_system_id] -
328 (*reactant.volume_fraction_prev)[chemical_system_id]);
331template <
typename Reactant>
333 Reactant
const& reactant,
334 std::vector<GlobalIndexType>
const& chemical_system_indices)
336 double const sum = std::accumulate(
337 chemical_system_indices.begin(), chemical_system_indices.end(), 0.0,
339 { return s + (*reactant.molality)[id]; });
340 return sum / chemical_system_indices.size();
344extern std::string
specifyFileName(std::string
const& project_file_name,
345 std::string
const& file_extension);
349 std::string
const& project_file_name,
350 std::string&& database,
351 std::unique_ptr<ChemicalSystem>&& chemical_system,
352 std::vector<ReactionRate>&& reaction_rates,
353 std::unique_ptr<UserPunch>&& user_punch,
354 std::unique_ptr<Output>&& output,
355 std::unique_ptr<Dump>&& dump,
357 bool const use_stream_mode,
358 int const num_chemistry_threads,
359 double const concentration_warning_threshold)
368 _dump(std::move(dump)),
383 "Parallel chemistry enabled: {} threads will be used for "
384 "PHREEQC calculations.",
388 "PhreeqcIO is configured for stream-based data exchange: input "
389 "and output will be exchanged via in-memory strings.");
403 "Failed to fly the flag for the specified file {:s} where "
404 "phreeqc will write output.",
405 _output->basic_output_setups.output_file);
437 std::vector<double>
const& concentrations,
447 auto const& solid_phase =
449 auto const& liquid_phase =
454 initializeReactantMolality(kinetic_reactant, chemical_system_id,
455 solid_phase, liquid_phase, medium, pos, t);
460 initializeReactantMolality(equilibrium_reactant, chemical_system_id,
461 solid_phase, liquid_phase, medium, pos, t);
466 initializeSiteMolality(exchanger, chemical_system_id, solid_phase, pos,
472 if (
auto const surface_site_ptr =
473 std::get_if<MoleBasedSurfaceSite>(&surface_site))
475 initializeSiteMolality(*surface_site_ptr, chemical_system_id,
476 solid_phase, pos, t);
482 std::vector<double>
const& concentrations,
494 auto const& solid_phase =
496 auto const& liquid_phase =
501 setReactantMolality(kinetic_reactant, chemical_system_id, solid_phase,
502 liquid_phase, vars, pos, t, dt);
507 setReactantMolality(equilibrium_reactant, chemical_system_id,
508 solid_phase, liquid_phase, vars, pos, t, dt);
529 DBUG(
"Executing speciation with file-based data exchange.");
536 int const component_id,
GlobalIndexType const chemical_system_id)
const
539 auto const& components = aqueous_solution.components;
540 auto const& H_plus_activity = aqueous_solution.H_plus_activity;
542 if (component_id <
static_cast<int>(components.size()))
544 return components[component_id].amount[chemical_system_id];
547 if (component_id !=
static_cast<int>(components.size()))
550 "Invalid component_id {:d}: must be in [0, {:d}] "
551 "(the last index represents H+ activity).",
552 component_id, components.size());
557 return H_plus_activity[chemical_system_id];
567 auto const& dump_file =
_dump->dump_file;
568 std::ifstream in(dump_file);
580 OGS_FATAL(
"Error when reading phreeqc dump file '{:s}'", dump_file);
587 std::string_view
const dump_content)
594 if (dump_content.empty())
599 "Dump content is empty, skipping aqueous solutions initialization "
614 OGS_FATAL(
"Could not open file '{:s}' for writing phreeqc inputs.",
618 out << std::scientific
619 << std::setprecision(std::numeric_limits<double>::max_digits10);
625 OGS_FATAL(
"Failed in generating phreeqc input file '{:s}'.",
659 std::size_t
const chemical_system_id,
660 std::size_t
const solution_id,
661 std::size_t
const prev_solution_id,
662 double const dt)
const
666 os <<
"SOLUTION " << solution_id <<
"\n";
669 if (
_dump && !
_dump->aqueous_solutions_prev.empty())
671 os <<
_dump->aqueous_solutions_prev[chemical_system_id] <<
"\n\n";
674 os <<
"USE solution none\n";
677 os <<
"USE solution " << solution_id <<
"\n\n";
679 auto const& equilibrium_reactants =
_chemical_system->equilibrium_reactants;
680 if (!equilibrium_reactants.empty() || fixing_pe)
682 os <<
"EQUILIBRIUM_PHASES " << solution_id <<
"\n";
683 for (
auto const& r : equilibrium_reactants)
685 r.print(os, chemical_system_id);
693 if (!kinetic_reactants.empty())
695 os <<
"KINETICS " << solution_id <<
"\n";
696 for (
auto const& k : kinetic_reactants)
698 k.print(os, chemical_system_id);
700 os <<
"-steps " << dt <<
"\n\n";
704 if (!surface.empty())
713 os <<
"SURFACE " << solution_id <<
"\n";
714 std::size_t
const aq_id =
715 (
_dump && !
_dump->aqueous_solutions_prev.empty()) ? prev_solution_id
717 os <<
"-equilibrate with solution " << aq_id <<
"\n";
719 if (std::holds_alternative<DensityBasedSurfaceSite>(surface.front()))
721 os <<
"-sites_units density\n";
725 os <<
"-sites_units absolute\n";
728 for (
auto const& surface_site : surface)
734 os << s.name <<
" " << s.site_density <<
" "
735 << s.specific_surface_area <<
" " << s.mass <<
"\n";
739 os << s.name <<
" " << (*s.molality)[chemical_system_id]
745 if (std::holds_alternative<MoleBasedSurfaceSite>(surface.front()))
749 os <<
"SAVE solution " << solution_id <<
"\n";
753 if (!exchangers.empty())
755 os <<
"EXCHANGE " << solution_id <<
"\n";
756 std::size_t
const aq_id =
757 (
_dump && !
_dump->aqueous_solutions_prev.empty()) ? prev_solution_id
759 os <<
"-equilibrate with solution " << aq_id <<
"\n";
760 for (
auto const& exchanger : exchangers)
762 exchanger.print(os, chemical_system_id);
764 os <<
"SAVE solution " << solution_id <<
"\n";
771 std::size_t
const chemical_system_id)
774 auto accepted_items = parseAndFilterChemicalData(
775 line, output.dropped_item_ids, chemical_system_id);
776 assert(accepted_items.size() == output.accepted_items.size());
784 for (std::size_t chemical_system_id = 0;
786 ++chemical_system_id)
788 std::size_t
const solution_id = chemical_system_id + 1;
789 std::size_t
const prev_solution_id =
792 prev_solution_id, phreeqc_io.
_dt);
795 if (phreeqc_io.
_dump)
805 INFO(
"Phreeqc: Executing chemical calculation.");
810 "Failed in performing speciation calculation with the generated "
811 "phreeqc input file '{:s}'.",
818 auto const& basic_output_setups =
_output->basic_output_setups;
819 auto const& phreeqc_result_file = basic_output_setups.output_file;
820 DBUG(
"Reading phreeqc results from file '{:s}'.", phreeqc_result_file);
821 std::ifstream in(phreeqc_result_file);
825 OGS_FATAL(
"Could not open phreeqc result file '{:s}'.",
826 phreeqc_result_file);
833 OGS_FATAL(
"Error when reading phreeqc result file '{:s}'",
834 phreeqc_result_file);
841 std::vector<double>
const& accepted_items, std::size_t chemical_system_id)
845 auto& components = aqueous_solution->components;
849 for (
int item_id = 0; item_id < static_cast<int>(accepted_items.size());
852 auto const& accepted_item = output.accepted_items[item_id];
853 auto const& item_name = accepted_item.name;
855 auto compare_by_name = [&item_name](
auto const& item)
856 {
return item.name == item_name; };
858 switch (accepted_item.item_type)
862 aqueous_solution->H_plus_activity[chemical_system_id] =
863 std::pow(10, -accepted_items[item_id]);
868 (*aqueous_solution->pe)[chemical_system_id] =
869 accepted_items[item_id];
875 components, compare_by_name,
878 OGS_FATAL(
"Could not find component '{:s}'.",
881 component.amount[chemical_system_id] = accepted_items[item_id];
887 equilibrium_reactants, compare_by_name,
890 OGS_FATAL(
"Could not find equilibrium reactant '{:s}'",
893 (*equilibrium_reactant.molality)[chemical_system_id] =
894 accepted_items[item_id];
900 kinetic_reactants, compare_by_name,
903 OGS_FATAL(
"Could not find kinetic reactant '{:s}'.",
906 (*kinetic_reactant.molality)[chemical_system_id] =
907 accepted_items[item_id];
913 auto const& secondary_variables =
916 secondary_variables, compare_by_name,
919 OGS_FATAL(
"Could not find secondary variable '{:s}'.",
922 (*secondary_variable.value)[chemical_system_id] =
923 accepted_items[item_id];
933 in.ignore(std::numeric_limits<std::streamsize>::max(),
'\n');
940 int const num_skipped_lines =
941 1 + (!surface.empty() ? 1 : 0) + (!exchangers.empty() ? 1 : 0);
943 for (std::size_t chemical_system_id = 0;
945 ++chemical_system_id)
947 for (
int i = 0; i < num_skipped_lines; ++i)
949 in.ignore(std::numeric_limits<std::streamsize>::max(),
'\n');
952 if (!std::getline(in, line))
955 "Error when reading calculation result of Solution {:d} "
956 "after the reaction.",
968 std::vector<std::string> component_names;
970 std::transform(components.begin(), components.end(),
971 std::back_inserter(component_names),
972 [](
auto const& c) { return c.name; });
974 component_names.emplace_back(
"H");
976 return component_names;
983 double const t,
double const dt)
987 updateReactantVolumeFraction(kinetic_reactant, chemical_system_id,
988 medium, pos, porosity, t, dt);
993 updateReactantVolumeFraction(equilibrium_reactant, chemical_system_id,
994 medium, pos, porosity, t, dt);
1005 setPorosityPostReaction(kinetic_reactant, chemical_system_id, medium,
1011 setPorosityPostReaction(equilibrium_reactant, chemical_system_id,
1017 std::size_t
const ele_id,
1018 std::vector<GlobalIndexType>
const& chemical_system_indices)
1022 (*kinetic_reactant.mesh_prop_molality)[ele_id] =
1023 averageReactantMolality(kinetic_reactant, chemical_system_indices);
1026 for (
auto const& equilibrium_reactant :
1029 (*equilibrium_reactant.mesh_prop_molality)[ele_id] =
1030 averageReactantMolality(equilibrium_reactant,
1031 chemical_system_indices);
1036 std::size_t
const chemical_system_id,
double const dt)
const
1038 std::ostringstream os;
1039 os << std::scientific
1040 << std::setprecision(std::numeric_limits<double>::max_digits10);
1047 std::size_t
const prev_solution_id =
1056 os <<
"-solution 1\n";
1064 std::size_t
const chemical_system_id)
1066 if (output_content.empty())
1068 OGS_FATAL(
"Empty output for chemical system {}.", chemical_system_id);
1071 StringViewLineIterator line_iter(output_content);
1072 std::string_view line;
1074 line_iter.getline(line);
1076 int const num_skipped_lines =
1079 line_iter.skip(num_skipped_lines);
1081 if (!line_iter.getline(line))
1084 "Error when reading calculation result of Solution {} after the "
1086 chemical_system_id);
1094 INFO(
"Phreeqc: Executing parallel chemical calculation with {} threads.",
1110 std::vector<std::size_t> failed_systems;
1111 std::mutex failed_mutex;
1113#pragma omp parallel num_threads(num_chemistry_threads_)
1116 int const thread_id = omp_get_thread_num();
1118 int const thread_id = 0;
1120 int const phreeqc_id =
instance_pool_->getInstanceForThread(thread_id);
1122#pragma omp for schedule(dynamic)
1123 for (std::ptrdiff_t i = 0;
1127 if (RunString(phreeqc_id, inputs[i].c_str()) != IPQ_OK)
1129 OutputErrorString(phreeqc_id);
1130 std::lock_guard<std::mutex> guard(failed_mutex);
1131 failed_systems.push_back(i);
1136 const char* output_ptr = GetSelectedOutputString(phreeqc_id);
1139 outputs[i] = output_ptr;
1145 const char* dump_ptr = GetDumpString(phreeqc_id);
1148 dumps[i] = dump_ptr;
1154 std::exception_ptr local_error;
1155 if (!failed_systems.empty())
1157 std::sort(failed_systems.begin(), failed_systems.end());
1159 for (
auto const id : failed_systems)
1165 ids += std::to_string(
id);
1167 local_error = std::make_exception_ptr(std::runtime_error(
1168 "Failed in performing speciation calculation for "
1169 "chemical system(s) " +
1182 if (
_dump && !dumps.empty())
1187 if (!dumps[i].empty())
1189 _dump->readDumpFromStringForSystem(dumps[i], i,
Definition of one reactive chemical system for PHREEQC coupling.
MathLib::EigenLisLinearSolver GlobalLinearSolver
GlobalMatrix::IndexType GlobalIndexType
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Per-system aqueous state exchanged with PHREEQC.
PHREEQC-backed ChemicalSolverInterface implementation.
std::vector< GlobalIndexType > chemical_system_index_map
GlobalLinearSolver & linear_solver
ChemicalSolverInterface(MeshLib::Mesh const &mesh, GlobalLinearSolver &linear_solver_)
void parseOutputForSystem(std::string_view output_content, std::size_t chemical_system_id)
std::string generateInputForSystem(std::size_t chemical_system_id, double const dt) const
int num_chemistry_threads_
void writeSystemBlock(std::ostream &os, std::size_t chemical_system_id, std::size_t solution_id, std::size_t prev_solution_id, double dt) const
std::string const _phreeqc_input_file
void updateSystemFromOutputLine(std::string_view line, std::size_t chemical_system_id)
double _concentration_warning_threshold
double getConcentration(int const component_id, GlobalIndexType const chemical_system_id) const override
void setAqueousSolutionsPrevFromDumpFile() override
std::unique_ptr< ChemicalSystem > _chemical_system
std::vector< ReactionRate > const _reaction_rates
std::unique_ptr< UserPunch > _user_punch
std::unique_ptr< PhreeqcInstancePool > instance_pool_
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
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, int num_chemistry_threads, double concentration_warning_threshold)
void executeSpeciationCalculationParallel(double const dt)
std::unique_ptr< Dump > const _dump
void initialize() override
std::unique_ptr< Output > const _output
void readOutputsFromFile()
std::size_t _num_chemical_systems
ClampingStats _clamping_totals
void computeSecondaryVariable(std::size_t const ele_id, std::vector< GlobalIndexType > const &chemical_system_indices) override
void updateChemicalSystemFromOutput(std::vector< double > const &accepted_items, std::size_t chemical_system_id)
void writeInputHeader(std::ostream &os) const
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
void executeSpeciationCalculation(double const dt) override
void setAqueousSolutionsPrevFromDumpString(std::string_view dump_content)
std::string const _database
std::vector< std::string > const getComponentList() const override
void updatePorosityPostReaction(GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const &medium, double &porosity) override
static int createInstance(std::string const &database)
void skip(int const num_lines)
StringViewLineIterator(std::string_view const text, std::size_t pos=0)
bool getline(std::string_view &line)
Phase const & phase(std::size_t index) const
Component const & component(std::size_t const &index) const
void allRanksThrowOrNone(std::exception_ptr const &exception, auto &&warning_callback)
ranges::range_reference_t< Range > findElementOrError(Range &range, std::predicate< ranges::range_reference_t< Range > > auto &&predicate, std::invocable auto error_callback)
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< double > parseAndFilterChemicalData(std::string_view const line, std::vector< int > const &dropped_item_ids, std::size_t const chemical_system_id)
void setPorosityPostReaction(Reactant &reactant, GlobalIndexType const &chemical_system_id, MaterialPropertyLib::Medium const &medium, double &porosity)
std::vector< std::string > extractItemsFromLine(std::string_view const line)
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)
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)
ClampingStats setAqueousSolution(std::vector< double > const &concentrations, std::size_t const chemical_system_id, AqueousSolution &aqueous_solution, double const warning_threshold)
std::istream & operator>>(std::istream &in, PhreeqcIO &phreeqc_io)