pyEQL 1.4.0rc9__cp313-cp313-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyEQL/__init__.py +50 -0
- pyEQL/_phreeqc.cpython-313-darwin.so +0 -0
- pyEQL/activity_correction.py +879 -0
- pyEQL/database/geothermal.dat +5693 -0
- pyEQL/database/llnl.dat +19305 -0
- pyEQL/database/phreeqc_license.txt +54 -0
- pyEQL/database/pyeql_db.json +35607 -0
- pyEQL/engines.py +1153 -0
- pyEQL/equilibrium.py +227 -0
- pyEQL/functions.py +281 -0
- pyEQL/phreeqc/__init__.py +5 -0
- pyEQL/phreeqc/bindings.cpp +84 -0
- pyEQL/phreeqc/core.py +239 -0
- pyEQL/phreeqc/database/Amm.dat +1968 -0
- pyEQL/phreeqc/database/CMakeLists.txt +32 -0
- pyEQL/phreeqc/database/ColdChem.dat +267 -0
- pyEQL/phreeqc/database/Concrete_PHR.dat +158 -0
- pyEQL/phreeqc/database/Concrete_PZ.dat +195 -0
- pyEQL/phreeqc/database/Kinec.v2.dat +12039 -0
- pyEQL/phreeqc/database/Kinec_v3.dat +12159 -0
- pyEQL/phreeqc/database/Makefile.am +28 -0
- pyEQL/phreeqc/database/Makefile.in +530 -0
- pyEQL/phreeqc/database/PHREEQC_ThermoddemV1.10_15Dec2020.dat +12965 -0
- pyEQL/phreeqc/database/Tipping_Hurley.dat +4137 -0
- pyEQL/phreeqc/database/__init__.py +0 -0
- pyEQL/phreeqc/database/core10.dat +6824 -0
- pyEQL/phreeqc/database/frezchem.dat +634 -0
- pyEQL/phreeqc/database/iso.dat +7235 -0
- pyEQL/phreeqc/database/llnl.dat +19310 -0
- pyEQL/phreeqc/database/minteq.dat +5654 -0
- pyEQL/phreeqc/database/minteq.v4.dat +13212 -0
- pyEQL/phreeqc/database/phreeqc.dat +1972 -0
- pyEQL/phreeqc/database/phreeqc_rates.dat +3158 -0
- pyEQL/phreeqc/database/pitzer.dat +1044 -0
- pyEQL/phreeqc/database/sit.dat +14348 -0
- pyEQL/phreeqc/database/wateq4f.dat +4036 -0
- pyEQL/phreeqc/ext/README.md +10 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/CMakeLists.txt +476 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/INSTALL +302 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/IPhreeqc.rc +61 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/IPhreeqcConfig.cmake.in +4 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/Makefile.am +8 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/Makefile.in +816 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/aclocal.m4 +1217 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/CTestScript.cmake +167 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/CSelectedOutput.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/IPhreeqc.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/IPhreeqcLib.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/IPhreeqc_interface_F.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/Var.c.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Dictionary.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/ExchComp.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Exchange.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/GasComp.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/GasPhase.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/ISolution.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/ISolutionComp.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/KineticsComp.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/NameDouble.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/NumKeyword.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/PBasic.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/PHRQ_io_output.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/PPassemblage.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/PPassemblageComp.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Phreeqc.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/PhreeqcKeywords/Keywords.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Pressure.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Reaction.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/ReadClass.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/SS.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/SSassemblage.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/SScomp.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/SelectedOutput.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Serializer.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Solution.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/SolutionIsotope.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/StorageBin.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/StorageBinList.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Surface.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/SurfaceCharge.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/SurfaceComp.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/System.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Temperature.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/Use.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/UserPunch.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/advection.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/basicsubs.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/cl1.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/common/PHRQ_base.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/common/PHRQ_io.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/common/Parser.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/common/Utils.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/cvdense.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/cvode.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/cxxKinetics.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/cxxMix.cxx.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/dense.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/dumper.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/gases.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/input.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/integrate.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/inverse.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/isotopes.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/kinetics.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/mainsubs.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/model.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/nvector.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/nvector_serial.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/parse.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/phqalloc.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/pitzer.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/pitzer_structures.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/prep.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/print.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/read.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/readtr.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/runner.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/sit.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/smalldense.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/spread.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/step.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/structures.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/sundialsmath.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/tally.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/tidy.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/transport.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/IPhreeqc.dir/src/phreeqcpp/utilities.cpp.o +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CTestTestfile.cmake +6 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/DartConfiguration.tcl +109 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/cmake_install.cmake +45 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/libIPhreeqc.a +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/ar-lib +270 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/compile +347 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/config.guess +1441 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/config.sub +1813 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/depcomp +791 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/install-sh +508 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/ltmain.sh +11156 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/missing +215 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/test-driver +148 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/configure +23867 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/configure.ac +136 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Amm.dat +1968 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/CMakeLists.txt +32 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/ColdChem.dat +267 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Concrete_PHR.dat +158 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Concrete_PZ.dat +195 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Kinec.v2.dat +12039 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Kinec_v3.dat +12159 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Makefile.am +28 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Makefile.in +530 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/PHREEQC_ThermoddemV1.10_15Dec2020.dat +12965 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Tipping_Hurley.dat +4137 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/core10.dat +6824 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/frezchem.dat +634 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/iso.dat +7235 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/llnl.dat +19310 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/minteq.dat +5654 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/minteq.v4.dat +13212 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/phreeqc.dat +1972 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/phreeqc_rates.dat +3158 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/pitzer.dat +1044 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/sit.dat +14348 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/wateq4f.dat +4036 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/CMakeLists.txt +35 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/IPhreeqc.pdf +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Makefile.am +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Makefile.in +545 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/NOTICE +51 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Phreeqc_2_1999_manual.pdf +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Phreeqc_3_2013_manual.pdf +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/README +428 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/RELEASE +7294 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8h.html +5096 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8h_source.html +389 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8hpp.html +83 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8hpp_source.html +478 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/Var_8h.html +318 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/Var_8h_source.html +200 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/bc_s.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/bdwn.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqc.html +2274 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqc.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqcStop.html +69 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqcStop.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/closed.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/dir_68267d1309a1af8e8297ef4c3efbcdba.html +68 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/doxygen.css +1440 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/doxygen.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/dynsections.js +97 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2blank.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2doc.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2folderclosed.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2folderopen.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2lastnode.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2link.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2mlastnode.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2mnode.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2node.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2plastnode.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2pnode.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2splitbar.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2vertline.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/index.html +58 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/jquery.js +31 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/nav_f.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/nav_g.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/nav_h.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/open.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/structVAR.html +143 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/sync_off.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/sync_on.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_a.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_b.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_h.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_s.png +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tabs.css +60 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/phreeqc3.chm +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/CMakeLists.txt +11 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/Makefile.am +88 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/Makefile.in +696 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/CMakeLists.txt +1 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/CMakeLists.txt +35 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/CMakeLists.txt.in +21 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/README.txt +44 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/advect.c +101 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/ic +17 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/phreeqc.dat +1579 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/CMakeLists.txt +10 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/README.txt +3 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/CMakeLists.txt +9 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/phreeqc.dat +1582 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/runphreeqc.xls +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/withcallback.xls +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/CMakeLists.txt +11 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/Gypsum.py +52 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/parallel_advect.py +465 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/phreeqc.dat +1582 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/pitzer.dat +790 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/wateq4f.dat +3846 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/CMakeLists.txt +1 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/CMakeLists.txt +35 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/CMakeLists.txt.in +20 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/README.txt +45 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/advect.cpp +110 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/ic +17 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/phreeqc.dat +1579 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/CMakeLists.txt +1 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/CMakeLists.txt +44 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/CMakeLists.txt.in +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/README.txt +45 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/advect.F90 +102 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/ic +17 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/phreeqc.dat +1579 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/CMakeLists.txt +26 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/CMakeLists.txt.in +20 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/README.txt +37 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/ex2 +26 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/main.cpp +20 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/phreeqc.dat +1837 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/post-install.cmake.in +7 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/CMakeLists.txt +185 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/FileTest.cpp +171 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/FileTest.h +34 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/Makefile.am +18 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/Makefile.in +466 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestCVar.cpp +9 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestIPhreeqc.cpp +4901 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestIPhreeqcLib.cpp +4644 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestSelectedOutput.cpp +669 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestVar.cpp +10 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/conv_fail.in +11 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/dump +42 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/iso.dat +7231 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/kinn20140218 +349 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/missing_e.dat +1556 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/multi_punch +105 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/multi_punch_no_set +102 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/phreeqc.dat.90a6449 +1935 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/phreeqc.dat.old +1556 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/libtool.m4 +8388 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/ltoptions.m4 +437 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/ltsugar.m4 +124 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/ltversion.m4 +23 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/lt~obsolete.m4 +99 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/resource.h +14 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/CSelectedOutput.cpp +401 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/CSelectedOutput.hxx +77 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/CVar.hxx +162 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Debug.h +12 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/ErrorReporter.hxx +70 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.cpp +1889 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.f.inc +91 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.f90.inc +603 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.h +2182 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.hpp +1027 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqcCallbacks.h +19 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqcF.f +653 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqcLib.cpp +1098 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc_interface.F90 +1283 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc_interface_F.cpp +535 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc_interface_F.h +162 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Makefile.am +210 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Makefile.in +1294 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/README.Fortran +17 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Var.c +84 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Var.h +152 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Version.h +36 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fimpl.h +282 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap.cpp +646 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap.h +163 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap1.cpp +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap2.cpp +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap3.cpp +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap4.cpp +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap5.cpp +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap6.cpp +25 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap7.cpp +25 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap8.cpp +24 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartHandler.cpp +225 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartHandler.h +59 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartObject.cpp +1382 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartObject.h +444 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/CurveObject.cpp +42 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/CurveObject.h +79 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Dictionary.cpp +41 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Dictionary.h +28 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ExchComp.cxx +398 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ExchComp.h +117 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Exchange.cxx +466 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Exchange.h +74 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Form1.h +1184 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Form1.resX +36 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasComp.cxx +265 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasComp.h +59 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasPhase.cxx +659 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasPhase.h +103 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolution.cxx +40 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolution.h +53 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolutionComp.cxx +202 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolutionComp.h +138 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/KineticsComp.cxx +318 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/KineticsComp.h +81 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NA.h +1 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NameDouble.cxx +537 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NameDouble.h +66 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NumKeyword.cxx +190 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NumKeyword.h +67 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PBasic.cpp +8350 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PBasic.h +572 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PHRQ_io_output.cpp +411 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblage.cxx +375 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblage.h +70 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblageComp.cxx +441 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblageComp.h +83 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Phreeqc.cpp +2087 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Phreeqc.h +2164 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PhreeqcKeywords/Keywords.cpp +242 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PhreeqcKeywords/Keywords.h +104 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Pressure.cxx +417 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Pressure.h +43 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Reaction.cxx +284 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Reaction.h +57 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ReadClass.cxx +1150 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SS.cxx +609 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SS.h +128 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SSassemblage.cxx +317 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SSassemblage.h +59 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SScomp.cxx +297 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SScomp.h +66 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SelectedOutput.cpp +115 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SelectedOutput.h +209 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Serializer.cxx +213 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Serializer.h +42 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Solution.cxx +1795 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Solution.h +154 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SolutionIsotope.cxx +333 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SolutionIsotope.h +85 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBin.cxx +1507 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBin.h +141 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBinList.cpp +358 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBinList.h +81 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Surface.cxx +837 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Surface.h +108 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceCharge.cxx +617 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceCharge.h +137 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceComp.cxx +509 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceComp.h +70 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/System.cxx +103 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/System.h +89 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Temperature.cxx +423 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Temperature.h +42 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Use.cpp +78 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Use.h +159 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/UserPunch.cpp +32 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/UserPunch.h +39 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ZedGraph.dll +0 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/advection.cpp +140 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/basicsubs.cpp +4333 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cl1.cpp +881 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_base.cxx +117 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_base.h +48 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_exports.h +20 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_io.cpp +914 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_io.h +207 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Parser.cxx +1331 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Parser.h +310 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Utils.cxx +263 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Utils.h +29 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/phrqtype.h +18 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvdense.cpp +566 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvdense.h +267 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvode.cpp +3939 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvode.h +940 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxKinetics.cxx +617 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxKinetics.h +78 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxMix.cxx +154 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxMix.h +58 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dense.cpp +175 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dense.h +341 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dumper.cpp +277 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dumper.h +60 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/gases.cpp +748 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/global_structures.h +1672 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/input.cpp +133 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/integrate.cpp +1219 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/inverse.cpp +5135 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/isotopes.cpp +1813 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/kinetics.cpp +3180 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/mainsubs.cpp +2320 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/model.cpp +5843 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector.cpp +272 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector.h +485 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector_serial.cpp +1032 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector_serial.h +369 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/parse.cpp +1044 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/phqalloc.cpp +316 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/phqalloc.h +47 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/pitzer.cpp +2709 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/pitzer_structures.cpp +225 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/prep.cpp +6267 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/print.cpp +3673 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/read.cpp +10245 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/readtr.cpp +1495 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/runner.cpp +158 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/runner.h +33 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sit.cpp +1684 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/smalldense.cpp +324 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/smalldense.h +261 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/spread.cpp +1309 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/step.cpp +1566 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/structures.cpp +3381 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sundialsmath.cpp +133 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sundialsmath.h +162 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sundialstypes.h +183 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/tally.cpp +1288 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/tidy.cpp +5600 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/transport.cpp +6403 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/utilities.cpp +1339 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/thread.h +64 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/CMakeLists.txt +133 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/Makefile.am +45 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/Makefile.in +1128 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/ex2.in +26 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/main.f90 +31 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/main77.f +6 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/main_fortran.cxx +8 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/phreeqc.dat.in +1556 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/test_c.c +148 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/test_cxx.cxx +152 -0
- pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/test_f90.F90 +328 -0
- pyEQL/phreeqc/iphreeqc_wrapper.cpp +75 -0
- pyEQL/phreeqc/solution.py +74 -0
- pyEQL/phreeqc/var.py +50 -0
- pyEQL/presets/Ringers lactate.yaml +20 -0
- pyEQL/presets/__init__.py +17 -0
- pyEQL/presets/normal saline.yaml +17 -0
- pyEQL/presets/rainwater.yaml +17 -0
- pyEQL/presets/seawater.yaml +29 -0
- pyEQL/presets/urine.yaml +26 -0
- pyEQL/presets/wastewater.yaml +21 -0
- pyEQL/py.typed +0 -0
- pyEQL/salt_ion_match.py +112 -0
- pyEQL/solute.py +163 -0
- pyEQL/solution.py +2714 -0
- pyEQL/utils.py +237 -0
- pyeql-1.4.0rc9.dist-info/METADATA +130 -0
- pyeql-1.4.0rc9.dist-info/RECORD +491 -0
- pyeql-1.4.0rc9.dist-info/WHEEL +6 -0
- pyeql-1.4.0rc9.dist-info/licenses/AUTHORS.md +21 -0
- pyeql-1.4.0rc9.dist-info/licenses/LICENSE.txt +20 -0
pyEQL/engines.py
ADDED
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyEQL engines for computing aqueous equilibria (e.g., speciation, redox, etc.).
|
|
3
|
+
|
|
4
|
+
:copyright: 2013-2024 by Ryan S. Kingsbury
|
|
5
|
+
:license: LGPL, see LICENSE for more details.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import logging
|
|
11
|
+
import math
|
|
12
|
+
import os
|
|
13
|
+
import warnings
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING, Literal
|
|
17
|
+
|
|
18
|
+
from phreeqpython import PhreeqPython
|
|
19
|
+
|
|
20
|
+
import pyEQL.activity_correction as ac
|
|
21
|
+
from pyEQL import ureg
|
|
22
|
+
from pyEQL.presets import ATMOSPHERE, EQUILIBRIUM_PHASE_AMOUNT
|
|
23
|
+
from pyEQL.utils import FormulaDict, standardize_formula
|
|
24
|
+
|
|
25
|
+
# These are the only elements that are allowed to have parenthetical oxidation states
|
|
26
|
+
# PHREEQC will ignore others (e.g., 'Na(1)')
|
|
27
|
+
SPECIAL_ELEMENTS = ["S", "C", "N", "Cu", "Fe", "Mn"]
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from pyEQL import solution
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
We determine if PhreeqPython from the phreeqpython package is actually
|
|
37
|
+
invocable on this platform, regardless of whether it is installed.
|
|
38
|
+
This flag helps us selectively skip phreeqpython-related tests inside
|
|
39
|
+
manylinux containers, among other uses.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _phreeqpython_available():
|
|
44
|
+
try:
|
|
45
|
+
PhreeqPython()
|
|
46
|
+
except: # noqa: E722
|
|
47
|
+
return False
|
|
48
|
+
else:
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
PHREEQPYTHON_AVAILABLE = _phreeqpython_available()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class EOS(ABC):
|
|
56
|
+
"""
|
|
57
|
+
Abstract base class for pyEQL equation of state classes.
|
|
58
|
+
|
|
59
|
+
The intent is that concrete implementations of this class make use of the
|
|
60
|
+
standalone functions available in pyEQL.activity_correction and pyEQL.equilibrium
|
|
61
|
+
as much as possible. This facilitates robust unit testing while allowing users
|
|
62
|
+
to "mix and match" or customize the various models as needed.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def get_activity_coefficient(self, solution: "solution.Solution", solute: str) -> ureg.Quantity:
|
|
67
|
+
"""
|
|
68
|
+
Return the *molal scale* activity coefficient of solute, given a Solution
|
|
69
|
+
object.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
solution: pyEQL Solution object
|
|
73
|
+
solute: str identifying the solute of interest
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Quantity: dimensionless quantity object
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError if the calculation cannot be completed, e.g. due to insufficient number of parameters.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def get_osmotic_coefficient(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
84
|
+
"""
|
|
85
|
+
Return the *molal scale* osmotic coefficient of a Solution.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
solution: pyEQL Solution object
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Quantity: dimensionless molal scale osmotic coefficient
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError if the calculation cannot be completed, e.g. due to insufficient number of parameters.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def get_solute_volume(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
99
|
+
"""
|
|
100
|
+
Return the volume of only the solutes.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
solution: pyEQL Solution object
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Quantity: solute volume in L
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
ValueError if the calculation cannot be completed, e.g. due to insufficient number of parameters.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def equilibrate(self, solution: "solution.Solution") -> None:
|
|
114
|
+
"""
|
|
115
|
+
Adjust the speciation and pH of a Solution object to achieve chemical equilibrium.
|
|
116
|
+
|
|
117
|
+
The Solution should be modified in-place, likely using add_moles / set_moles, etc.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
solution: pyEQL Solution object
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Nothing. The speciation of the Solution is modified in-place.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ValueError if the calculation cannot be completed, e.g. due to insufficient number of parameters or lack of convergence.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class IdealEOS(EOS):
|
|
131
|
+
"""Ideal solution equation of state engine."""
|
|
132
|
+
|
|
133
|
+
def get_activity_coefficient(self, solution: "solution.Solution", solute: str) -> ureg.Quantity:
|
|
134
|
+
"""
|
|
135
|
+
Return the *molal scale* activity coefficient of solute, given a Solution
|
|
136
|
+
object.
|
|
137
|
+
"""
|
|
138
|
+
return ureg.Quantity(1, "dimensionless")
|
|
139
|
+
|
|
140
|
+
def get_osmotic_coefficient(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
141
|
+
"""
|
|
142
|
+
Return the *molal scale* osmotic coefficient of solute, given a Solution
|
|
143
|
+
object.
|
|
144
|
+
"""
|
|
145
|
+
return ureg.Quantity(1, "dimensionless")
|
|
146
|
+
|
|
147
|
+
def get_solute_volume(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
148
|
+
"""Return the volume of the solutes."""
|
|
149
|
+
return ureg.Quantity(0, "L")
|
|
150
|
+
|
|
151
|
+
def equilibrate(self, solution: "solution.Solution") -> None:
|
|
152
|
+
"""Adjust the speciation of a Solution object to achieve chemical equilibrium."""
|
|
153
|
+
warnings.warn("equilibrate() has no effect in IdealEOS!")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class NativeEOS(EOS):
|
|
158
|
+
"""
|
|
159
|
+
pyEQL's native EOS. Uses the Pitzer model when possible, falls
|
|
160
|
+
back to other models (e.g. Debye-Huckel) based on ionic strength
|
|
161
|
+
if sufficient parameters are not available.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(
|
|
165
|
+
self,
|
|
166
|
+
phreeqc_db: Literal[
|
|
167
|
+
"phreeqc.dat", "vitens.dat", "wateq4f_PWN.dat", "pitzer.dat", "llnl.dat", "geothermal.dat"
|
|
168
|
+
] = "llnl.dat",
|
|
169
|
+
) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Args:
|
|
172
|
+
phreeqc_db: Name of the PHREEQC database file to use for solution thermodynamics
|
|
173
|
+
and speciation calculations. Generally speaking, `llnl.dat` is recommended
|
|
174
|
+
for moderate salinity water and prediction of mineral solubilities,
|
|
175
|
+
`wateq4f_PWN.dat` is recommended for low to moderate salinity waters. It is
|
|
176
|
+
similar to vitens.dat but has many more species. `pitzer.dat` is recommended
|
|
177
|
+
when accurate activity coefficients in solutions above 1 M TDS are desired, but
|
|
178
|
+
it has fewer species than the other databases. `llnl.dat` and `geothermal.dat`
|
|
179
|
+
may offer improved prediction of LSI but currently these databases are not
|
|
180
|
+
usable because they do not allow for conductivity calculations.
|
|
181
|
+
"""
|
|
182
|
+
self.phreeqc_db = phreeqc_db
|
|
183
|
+
# database files in this list are not distributed with phreeqpython
|
|
184
|
+
self.db_path = (
|
|
185
|
+
Path(os.path.dirname(__file__)) / "database" if self.phreeqc_db in ["llnl.dat", "geothermal.dat"] else None
|
|
186
|
+
)
|
|
187
|
+
# create the PhreeqcPython instance
|
|
188
|
+
# try/except added to catch unsupported architectures, such as Apple Silicon
|
|
189
|
+
try:
|
|
190
|
+
self.pp = PhreeqPython(database=self.phreeqc_db, database_directory=self.db_path)
|
|
191
|
+
except OSError:
|
|
192
|
+
logger.error(
|
|
193
|
+
"OSError encountered when trying to instantiate phreeqpython. Most likely this means you"
|
|
194
|
+
" are running on an architecture that is not supported by PHREEQC, such as Apple M1/M2 chips."
|
|
195
|
+
" pyEQL will work, but equilibrate() will have no effect."
|
|
196
|
+
)
|
|
197
|
+
# attributes to hold the PhreeqPython solution.
|
|
198
|
+
self.ppsol = None
|
|
199
|
+
# store the solution composition to see whether we need to re-instantiate the solution
|
|
200
|
+
self._stored_comp = None
|
|
201
|
+
|
|
202
|
+
def _setup_ppsol(self, solution: "solution.Solution") -> None:
|
|
203
|
+
"""Helper method to set up a PhreeqPython solution for subsequent analysis."""
|
|
204
|
+
self._stored_comp = solution.components.copy()
|
|
205
|
+
solv_mass = solution.solvent_mass.to("kg").magnitude
|
|
206
|
+
# inherit bulk solution properties
|
|
207
|
+
d = {
|
|
208
|
+
"temp": solution.temperature.to("degC").magnitude,
|
|
209
|
+
"units": "mol/kgw", # to avoid confusion about volume, use mol/kgw which seems more robust in PHREEQC
|
|
210
|
+
"pH": solution.pH,
|
|
211
|
+
"pe": solution.pE,
|
|
212
|
+
"redox": "pe", # hard-coded to use the pe
|
|
213
|
+
# PHREEQC will assume 1 kg if not specified, there is also no direct way to specify volume, so we
|
|
214
|
+
# really have to specify the solvent mass in 1 liter of solution
|
|
215
|
+
"water": solv_mass,
|
|
216
|
+
}
|
|
217
|
+
if solution.balance_charge == "pH":
|
|
218
|
+
d["pH"] = str(d["pH"]) + " charge"
|
|
219
|
+
if solution.balance_charge == "pE":
|
|
220
|
+
d["pe"] = str(d["pe"]) + " charge"
|
|
221
|
+
|
|
222
|
+
# add the composition to the dict
|
|
223
|
+
# also, skip H and O
|
|
224
|
+
for el, mol in solution.get_el_amt_dict().items():
|
|
225
|
+
# CAUTION - care must be taken to avoid unintended behavior here. get_el_amt_dict() will return
|
|
226
|
+
# all distinct oxi states of each element present. If there are elements present whose oxi states
|
|
227
|
+
# are NOT recognized by PHREEQC (via SPECIAL_ELEMENTS) then the amount of only 1 oxi state will be
|
|
228
|
+
# entered into the composition dict. This can especially cause problems after equilibrate() has already
|
|
229
|
+
# been called once. For example, equilibrating a simple NaCl solution generates Cl species that are assigned
|
|
230
|
+
# various oxidations states, -1 mostly, but also 1, 2, and 3. Since the concentrations of everything
|
|
231
|
+
# except the -1 oxi state are tiny, this can result in Cl "disappearing" from the solution if
|
|
232
|
+
# equlibrate is called again. It also causes non-determinism, because the amount is taken from whatever
|
|
233
|
+
# oxi state happens to be iterated through last.
|
|
234
|
+
|
|
235
|
+
# strip off the oxi state
|
|
236
|
+
bare_el = el.split("(")[0]
|
|
237
|
+
if bare_el in SPECIAL_ELEMENTS:
|
|
238
|
+
# PHREEQC will ignore float-formatted oxi states. Need to make sure we are
|
|
239
|
+
# passing, e.g. 'C(4)' and not 'C(4.0)'
|
|
240
|
+
key = f"{bare_el}({int(float(el.split('(')[-1].split(')')[0]))})"
|
|
241
|
+
elif bare_el in ["H", "O"]:
|
|
242
|
+
continue
|
|
243
|
+
else:
|
|
244
|
+
key = bare_el
|
|
245
|
+
|
|
246
|
+
if key in d:
|
|
247
|
+
# when multiple oxi states for the same (non-SPECIAL) element are present, make sure to
|
|
248
|
+
# add all their amounts together
|
|
249
|
+
d[key] += str(mol / solv_mass)
|
|
250
|
+
else:
|
|
251
|
+
d[key] = str(mol / solv_mass)
|
|
252
|
+
|
|
253
|
+
# tell PHREEQC which species to use for charge balance
|
|
254
|
+
if solution.balance_charge is not None and solution._cb_species in solution.get_components_by_element()[el]:
|
|
255
|
+
d[key] += " charge"
|
|
256
|
+
|
|
257
|
+
# create the PHREEQC solution object
|
|
258
|
+
try:
|
|
259
|
+
ppsol = self.pp.add_solution(d)
|
|
260
|
+
except Exception as e:
|
|
261
|
+
print(d)
|
|
262
|
+
# catch problems with the input to phreeqc
|
|
263
|
+
raise ValueError(
|
|
264
|
+
"There is a problem with your input. The error message received from "
|
|
265
|
+
f" phreeqpython is:\n\n {e}\n Check your input arguments, especially "
|
|
266
|
+
"the composition dictionary, and try again."
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
self.ppsol = ppsol
|
|
270
|
+
|
|
271
|
+
def _destroy_ppsol(self) -> None:
|
|
272
|
+
"""Remove the PhreeqPython solution from memory."""
|
|
273
|
+
if self.ppsol is not None:
|
|
274
|
+
self.ppsol.forget()
|
|
275
|
+
self.ppsol = None
|
|
276
|
+
|
|
277
|
+
def get_activity_coefficient(self, solution: "solution.Solution", solute: str):
|
|
278
|
+
r"""
|
|
279
|
+
Whenever the appropriate parameters are available, the Pitzer model [may11]_ is used.
|
|
280
|
+
If no Pitzer parameters are available, then the appropriate equations are selected
|
|
281
|
+
according to the following logic: [stumm96]_.
|
|
282
|
+
|
|
283
|
+
I <= 0.0005: Debye-Huckel equation
|
|
284
|
+
0.005 < I <= 0.1: Guntelberg approximation
|
|
285
|
+
0.1 < I <= 0.5: Davies equation
|
|
286
|
+
I > 0.5: Raises a warning and returns activity coefficient = 1
|
|
287
|
+
|
|
288
|
+
The ionic strength, activity coefficients, and activities are all
|
|
289
|
+
calculated based on the molal (mol/kg) concentration scale. If a different
|
|
290
|
+
scale is given as input, then the molal-scale activity coefficient :math:`\gamma_\pm` is
|
|
291
|
+
converted according to [rbs68]_
|
|
292
|
+
|
|
293
|
+
.. math:: f_\pm = \gamma_\pm * (1 + M_w \sum_i \nu_i m_i)
|
|
294
|
+
|
|
295
|
+
.. math:: y_\pm = \frac{m \rho_w}{C \gamma_\pm}
|
|
296
|
+
|
|
297
|
+
where :math:`f_\pm` is the rational activity coefficient, :math:`M_w` is
|
|
298
|
+
the molecular weight of water, the summation represents the total molality of
|
|
299
|
+
all solute species, :math:`y_\pm` is the molar activity coefficient,
|
|
300
|
+
:math:`\rho_w` is the density of pure water, :math:`m` and :math:`C` are
|
|
301
|
+
the molal and molar concentrations of the chosen salt (not individual solute), respectively.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
solute: String representing the name of the solute of interest
|
|
305
|
+
scale: The concentration scale for the returned activity coefficient.
|
|
306
|
+
Valid options are "molal", "molar", and "rational" (i.e., mole fraction).
|
|
307
|
+
By default, the molal scale activity coefficient is returned.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
The mean ion activity coefficient of the solute in question on the selected scale.
|
|
311
|
+
|
|
312
|
+
Notes:
|
|
313
|
+
For multicomponent mixtures, pyEQL implements the "effective Pitzer model"
|
|
314
|
+
presented by Mistry et al. [mistry13]_. In this model, the activity coefficient
|
|
315
|
+
of a salt in a multicomponent mixture is calculated using an "effective
|
|
316
|
+
molality," which is the molality that would result in a single-salt
|
|
317
|
+
mixture with the same total ionic strength as the multicomponent solution.
|
|
318
|
+
|
|
319
|
+
.. math:: m_{effective} = \frac{2 I}{(\nu_{+} z_{+}^2 + \nu_{-}- z_{-}^2)}
|
|
320
|
+
|
|
321
|
+
References:
|
|
322
|
+
.. [may11] May, P. M., Rowland, D., Hefter, G., & Königsberger, E. (2011).
|
|
323
|
+
A Generic and Updatable Pitzer Characterization of Aqueous Binary Electrolyte Solutions at 1 bar
|
|
324
|
+
and 25 °C. *Journal of Chemical & Engineering Data*, 56(12), 5066-5077. doi:10.1021/je2009329
|
|
325
|
+
|
|
326
|
+
.. [stumm96] Stumm, Werner and Morgan, James J. *Aquatic Chemistry*, 3rd ed,
|
|
327
|
+
pp 165. Wiley Interscience, 1996.
|
|
328
|
+
|
|
329
|
+
.. [rbs68] Robinson, R. A.; Stokes, R. H. Electrolyte Solutions: Second Revised
|
|
330
|
+
Edition; Butterworths: London, 1968, p.32.
|
|
331
|
+
|
|
332
|
+
See Also:
|
|
333
|
+
:attr:`pyEQL.solution.Solution.ionic_strength`
|
|
334
|
+
:func:`pyEQL.activity_correction.get_activity_coefficient_debyehuckel`
|
|
335
|
+
:func:`pyEQL.activity_correction.get_activity_coefficient_guntelberg`
|
|
336
|
+
:func:`pyEQL.activity_correction.get_activity_coefficient_davies`
|
|
337
|
+
:func:`pyEQL.activity_correction.get_activity_coefficient_pitzer`
|
|
338
|
+
"""
|
|
339
|
+
# identify the predominant salt that this ion is a member of
|
|
340
|
+
salt = None
|
|
341
|
+
rform = standardize_formula(solute)
|
|
342
|
+
for d in solution.get_salt_dict().values():
|
|
343
|
+
if rform == d["salt"].cation or rform == d["salt"].anion:
|
|
344
|
+
salt = d["salt"]
|
|
345
|
+
break
|
|
346
|
+
|
|
347
|
+
# show an error if no salt can be found that contains the solute
|
|
348
|
+
if salt is None:
|
|
349
|
+
logger.error(f"No salts found that contain solute {solute}. Returning unit activity coefficient.")
|
|
350
|
+
return ureg.Quantity(1, "dimensionless")
|
|
351
|
+
|
|
352
|
+
# use the Pitzer model for higher ionic strength, if the parameters are available
|
|
353
|
+
# search for Pitzer parameters
|
|
354
|
+
param = solution.get_property(salt.formula, "model_parameters.activity_pitzer")
|
|
355
|
+
if param is not None:
|
|
356
|
+
# TODO - consider re-enabling a log message recording what salt(s) are used as basis for activity calculation
|
|
357
|
+
logger.info(f"Calculating activity coefficient based on parent salt {salt.formula}")
|
|
358
|
+
|
|
359
|
+
# determine alpha1 and alpha2 based on the type of salt
|
|
360
|
+
# see the May reference for the rules used to determine
|
|
361
|
+
# alpha1 and alpha2 based on charge
|
|
362
|
+
if salt.z_cation >= 2 and salt.z_anion <= -2:
|
|
363
|
+
if salt.z_cation >= 3 or salt.z_anion <= -3:
|
|
364
|
+
alpha1 = 2.0
|
|
365
|
+
alpha2 = 50.0
|
|
366
|
+
else:
|
|
367
|
+
alpha1 = 1.4
|
|
368
|
+
alpha2 = 12
|
|
369
|
+
else:
|
|
370
|
+
alpha1 = 2.0
|
|
371
|
+
alpha2 = 0.0
|
|
372
|
+
|
|
373
|
+
# determine the average molality of the salt
|
|
374
|
+
# this is necessary for solutions inside e.g. an ion exchange
|
|
375
|
+
# membrane, where the cation and anion concentrations may be
|
|
376
|
+
# unequal
|
|
377
|
+
# molality = (solution.get_amount(salt.cation,'mol/kg')/salt.nu_cation+solution.get_amount(salt.anion,'mol/kg')/salt.nu_anion)/2
|
|
378
|
+
|
|
379
|
+
# determine the effective molality of the salt in the solution
|
|
380
|
+
molality = salt.get_effective_molality(solution.ionic_strength)
|
|
381
|
+
|
|
382
|
+
activity_coefficient = ac.get_activity_coefficient_pitzer(
|
|
383
|
+
solution.ionic_strength,
|
|
384
|
+
molality,
|
|
385
|
+
alpha1,
|
|
386
|
+
alpha2,
|
|
387
|
+
ureg.Quantity(param["Beta0"]["value"]).magnitude,
|
|
388
|
+
ureg.Quantity(param["Beta1"]["value"]).magnitude,
|
|
389
|
+
ureg.Quantity(param["Beta2"]["value"]).magnitude,
|
|
390
|
+
ureg.Quantity(param["Cphi"]["value"]).magnitude,
|
|
391
|
+
salt.z_cation,
|
|
392
|
+
salt.z_anion,
|
|
393
|
+
salt.nu_cation,
|
|
394
|
+
salt.nu_anion,
|
|
395
|
+
str(solution.temperature),
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
logger.debug(
|
|
399
|
+
f"Calculated activity coefficient of species {solute} as {activity_coefficient} based on salt"
|
|
400
|
+
f" {salt} using Pitzer model"
|
|
401
|
+
)
|
|
402
|
+
molal = activity_coefficient
|
|
403
|
+
|
|
404
|
+
# for very low ionic strength, use the Debye-Huckel limiting law
|
|
405
|
+
elif solution.ionic_strength.magnitude <= 0.005:
|
|
406
|
+
logger.debug(
|
|
407
|
+
f"Ionic strength = {solution.ionic_strength}. Using Debye-Huckel to calculate activity coefficient."
|
|
408
|
+
)
|
|
409
|
+
molal = ac.get_activity_coefficient_debyehuckel(
|
|
410
|
+
solution.ionic_strength,
|
|
411
|
+
solution.get_property(solute, "charge"),
|
|
412
|
+
str(solution.temperature),
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# use the Guntelberg approximation for 0.005 < I < 0.1
|
|
416
|
+
elif solution.ionic_strength.magnitude <= 0.1:
|
|
417
|
+
logger.debug(
|
|
418
|
+
f"Ionic strength = {solution.ionic_strength}. Using Guntelberg to calculate activity coefficient."
|
|
419
|
+
)
|
|
420
|
+
molal = ac.get_activity_coefficient_guntelberg(
|
|
421
|
+
solution.ionic_strength,
|
|
422
|
+
solution.get_property(solute, "charge"),
|
|
423
|
+
str(solution.temperature),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# use the Davies equation for 0.1 < I < 0.5
|
|
427
|
+
elif solution.ionic_strength.magnitude <= 0.5:
|
|
428
|
+
logger.debug(
|
|
429
|
+
f"Ionic strength = {solution.ionic_strength}. Using Davies equation to calculate activity coefficient."
|
|
430
|
+
)
|
|
431
|
+
molal = ac.get_activity_coefficient_davies(
|
|
432
|
+
solution.ionic_strength,
|
|
433
|
+
solution.get_property(solute, "charge"),
|
|
434
|
+
str(solution.temperature),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
else:
|
|
438
|
+
logger.error(
|
|
439
|
+
f"Ionic strength too high to estimate activity for species {solute}. Specify parameters for Pitzer "
|
|
440
|
+
"model. Returning unit activity coefficient"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
molal = ureg.Quantity(1, "dimensionless")
|
|
444
|
+
|
|
445
|
+
return molal
|
|
446
|
+
|
|
447
|
+
def get_osmotic_coefficient(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
448
|
+
r"""
|
|
449
|
+
Return the *molal scale* osmotic coefficient of solute, given a Solution
|
|
450
|
+
object.
|
|
451
|
+
|
|
452
|
+
Osmotic coefficient is calculated using the Pitzer model. [may11]_ If appropriate parameters for
|
|
453
|
+
the model are not available, then pyEQL raises a WARNING and returns an osmotic
|
|
454
|
+
coefficient of 1.
|
|
455
|
+
|
|
456
|
+
If the 'rational' scale is given as input, then the molal-scale osmotic
|
|
457
|
+
coefficient :math:`\phi` is converted according to [rbs68]_
|
|
458
|
+
|
|
459
|
+
.. math:: g = - \phi M_{w} \frac{\sum_{i} \nu_{i} m_{i}}{\ln x_{w}}
|
|
460
|
+
|
|
461
|
+
where :math:`g` is the rational osmotic coefficient, :math:`M_{w}` is
|
|
462
|
+
the molecular weight of water, the summation represents the total molality of
|
|
463
|
+
all solute species, and :math:`x_{w}` is the mole fraction of water.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
scale: The concentration scale for the returned osmotic coefficient. Valid options are "molal",
|
|
467
|
+
"rational" (i.e., mole fraction), and "fugacity". By default, the molal scale osmotic
|
|
468
|
+
coefficient is returned.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Quantity:
|
|
472
|
+
The osmotic coefficient
|
|
473
|
+
|
|
474
|
+
See Also:
|
|
475
|
+
:meth:`pyEQL.solution.Solution.get_water_activity`
|
|
476
|
+
:meth:`pyEQL.solution.Solution.get_salt`
|
|
477
|
+
:attr:`pyEQL.solution.Solution.ionic_strength`
|
|
478
|
+
|
|
479
|
+
Notes:
|
|
480
|
+
For multicomponent mixtures, pyEQL adopts the "effective Pitzer model"
|
|
481
|
+
presented by Mistry et al. [mistry13]_. In this approach, the osmotic coefficient of
|
|
482
|
+
each individual salt is calculated using the normal Pitzer model based
|
|
483
|
+
on its respective concentration. Then, an effective osmotic coefficient
|
|
484
|
+
is calculated as the concentration-weighted average of the individual
|
|
485
|
+
osmotic coefficients.
|
|
486
|
+
|
|
487
|
+
For example, in a mixture of 0.5 M NaCl and 0.5 M KBr, one would calculate
|
|
488
|
+
the osmotic coefficient for each salt using a concentration of 0.5 M and
|
|
489
|
+
an ionic strength of 1 M. Then, one would average the two resulting
|
|
490
|
+
osmotic coefficients to obtain an effective osmotic coefficient for the
|
|
491
|
+
mixture.
|
|
492
|
+
|
|
493
|
+
(Note: in the paper referenced below, the effective osmotic coefficient is determined by weighting
|
|
494
|
+
using the "effective molality" rather than the true molality. Subsequent checking and correspondence with
|
|
495
|
+
the author confirmed that the weight factor should be the true molality, and that is what is implemented
|
|
496
|
+
in pyEQL.)
|
|
497
|
+
|
|
498
|
+
Examples:
|
|
499
|
+
>>> s1 = pyEQL.Solution({'Na+': '0.2 mol/kg', 'Cl-': '0.2 mol/kg'})
|
|
500
|
+
>>> s1.get_osmotic_coefficient()
|
|
501
|
+
<Quantity(0.923715281, 'dimensionless')>
|
|
502
|
+
|
|
503
|
+
>>> s1 = pyEQL.Solution({'Mg+2': '0.3 mol/kg', 'Cl-': '0.6 mol/kg'},temperature='30 degC')
|
|
504
|
+
>>> s1.get_osmotic_coefficient()
|
|
505
|
+
<Quantity(0.891409618, 'dimensionless')>
|
|
506
|
+
|
|
507
|
+
References:
|
|
508
|
+
[may11]
|
|
509
|
+
|
|
510
|
+
May, P. M., Rowland, D., Hefter, G., & Königsberger, E. (2011).
|
|
511
|
+
A Generic and Updatable Pitzer Characterization of Aqueous Binary Electrolyte Solutions at 1 bar
|
|
512
|
+
and 25 °C. Journal of Chemical & Engineering Data, 56(12), 5066-5077. doi:10.1021/je2009329
|
|
513
|
+
|
|
514
|
+
[rbs68]
|
|
515
|
+
|
|
516
|
+
Robinson, R. A.; Stokes, R. H. Electrolyte Solutions: Second Revised Edition; Butterworths: London, 1968,
|
|
517
|
+
p.32.
|
|
518
|
+
|
|
519
|
+
[mistry13]
|
|
520
|
+
|
|
521
|
+
Mistry, K. H.; Hunter, H. a.; Lienhard V, J. H. Effect of composition and nonideal solution
|
|
522
|
+
behavior on desalination calculations for mixed electrolyte solutions with comparison to
|
|
523
|
+
seawater. Desalination 2013, 318, 34-47.
|
|
524
|
+
|
|
525
|
+
"""
|
|
526
|
+
ionic_strength = solution.ionic_strength
|
|
527
|
+
|
|
528
|
+
effective_osmotic_sum = 0
|
|
529
|
+
molality_sum = 0
|
|
530
|
+
|
|
531
|
+
# loop through all the salts in the solution, calculate the osmotic
|
|
532
|
+
# coefficint for each, and average them into an effective osmotic
|
|
533
|
+
# coefficient
|
|
534
|
+
for d in solution.get_salt_dict().values():
|
|
535
|
+
item = d["salt"]
|
|
536
|
+
# determine alpha1 and alpha2 based on the type of salt
|
|
537
|
+
# see the May reference for the rules used to determine
|
|
538
|
+
# alpha1 and alpha2 based on charge
|
|
539
|
+
if item.z_cation >= 2 and item.z_anion <= -2:
|
|
540
|
+
if item.z_cation >= 3 or item.z_anion <= -3:
|
|
541
|
+
alpha1 = 2.0
|
|
542
|
+
alpha2 = 50.0
|
|
543
|
+
else:
|
|
544
|
+
alpha1 = 1.4
|
|
545
|
+
alpha2 = 12.0
|
|
546
|
+
else:
|
|
547
|
+
alpha1 = 2.0
|
|
548
|
+
alpha2 = 0
|
|
549
|
+
|
|
550
|
+
# set the concentration as the average concentration of the cation and
|
|
551
|
+
# anion in the salt, accounting for stoichiometry
|
|
552
|
+
# concentration = (solution.get_amount(Salt.cation,'mol/kg')/Salt.nu_cation + \
|
|
553
|
+
# solution.get_amount(Salt.anion,'mol/kg')/Salt.nu_anion)/2
|
|
554
|
+
|
|
555
|
+
# get the effective molality of the salt
|
|
556
|
+
concentration = ureg.Quantity(d["mol"], "mol") / solution.solvent_mass
|
|
557
|
+
|
|
558
|
+
molality_sum += concentration
|
|
559
|
+
|
|
560
|
+
param = solution.get_property(item.formula, "model_parameters.activity_pitzer")
|
|
561
|
+
if param is not None:
|
|
562
|
+
osmotic_coefficient = ac.get_osmotic_coefficient_pitzer(
|
|
563
|
+
ionic_strength,
|
|
564
|
+
concentration,
|
|
565
|
+
alpha1,
|
|
566
|
+
alpha2,
|
|
567
|
+
ureg.Quantity(param["Beta0"]["value"]).magnitude,
|
|
568
|
+
ureg.Quantity(param["Beta1"]["value"]).magnitude,
|
|
569
|
+
ureg.Quantity(param["Beta2"]["value"]).magnitude,
|
|
570
|
+
ureg.Quantity(param["Cphi"]["value"]).magnitude,
|
|
571
|
+
item.z_cation,
|
|
572
|
+
item.z_anion,
|
|
573
|
+
item.nu_cation,
|
|
574
|
+
item.nu_anion,
|
|
575
|
+
str(solution.temperature),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
logger.debug(
|
|
579
|
+
f"Calculated osmotic coefficient of water as {osmotic_coefficient} based on salt "
|
|
580
|
+
f"{item.formula} using Pitzer model"
|
|
581
|
+
)
|
|
582
|
+
effective_osmotic_sum += concentration * osmotic_coefficient
|
|
583
|
+
|
|
584
|
+
else:
|
|
585
|
+
logger.debug(
|
|
586
|
+
f"Returning unit osmotic coefficient for salt {item.formula} because Pitzer parameters are not"
|
|
587
|
+
"available in database."
|
|
588
|
+
)
|
|
589
|
+
effective_osmotic_sum += concentration * 1
|
|
590
|
+
|
|
591
|
+
try:
|
|
592
|
+
return effective_osmotic_sum / molality_sum
|
|
593
|
+
except ZeroDivisionError:
|
|
594
|
+
# this means the solution is empty
|
|
595
|
+
return ureg.Quantity(1.0)
|
|
596
|
+
|
|
597
|
+
def get_solute_volume(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
598
|
+
"""Return the volume of the solutes."""
|
|
599
|
+
# identify the predominant salt in the solution
|
|
600
|
+
salt = solution.get_salt()
|
|
601
|
+
solute_vol = ureg.Quantity(0, "L")
|
|
602
|
+
|
|
603
|
+
# use the pitzer approach if parameters are available
|
|
604
|
+
pitzer_calc = False
|
|
605
|
+
param = None if salt is None else solution.get_property(salt.formula, "model_parameters.molar_volume_pitzer")
|
|
606
|
+
|
|
607
|
+
if param is not None:
|
|
608
|
+
# determine the average molality of the salt
|
|
609
|
+
# this is necessary for solutions inside e.g. an ion exchange
|
|
610
|
+
# membrane, where the cation and anion concentrations may be
|
|
611
|
+
# unequal
|
|
612
|
+
molality = (solution.get_amount(salt.cation, "mol/kg") + solution.get_amount(salt.anion, "mol/kg")) / 2
|
|
613
|
+
|
|
614
|
+
# determine alpha1 and alpha2 based on the type of salt
|
|
615
|
+
# see the May reference for the rules used to determine
|
|
616
|
+
# alpha1 and alpha2 based on charge
|
|
617
|
+
if salt.z_cation >= 2 and salt.z_anion <= -2:
|
|
618
|
+
if salt.z_cation >= 3 or salt.z_anion <= -3:
|
|
619
|
+
alpha1 = 2.0
|
|
620
|
+
alpha2 = 50.0
|
|
621
|
+
else:
|
|
622
|
+
alpha1 = 1.4
|
|
623
|
+
alpha2 = 12
|
|
624
|
+
else:
|
|
625
|
+
alpha1 = 2.0
|
|
626
|
+
alpha2 = 0.0
|
|
627
|
+
|
|
628
|
+
apparent_vol = ac.get_apparent_volume_pitzer(
|
|
629
|
+
solution.ionic_strength,
|
|
630
|
+
molality,
|
|
631
|
+
alpha1,
|
|
632
|
+
alpha2,
|
|
633
|
+
ureg.Quantity(param["Beta0"]["value"]).magnitude,
|
|
634
|
+
ureg.Quantity(param["Beta1"]["value"]).magnitude,
|
|
635
|
+
ureg.Quantity(param["Beta2"]["value"]).magnitude,
|
|
636
|
+
ureg.Quantity(param["Cphi"]["value"]).magnitude,
|
|
637
|
+
ureg.Quantity(param["V_o"]["value"]).magnitude,
|
|
638
|
+
salt.z_cation,
|
|
639
|
+
salt.z_anion,
|
|
640
|
+
salt.nu_cation,
|
|
641
|
+
salt.nu_anion,
|
|
642
|
+
str(solution.temperature),
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
solute_vol += (
|
|
646
|
+
apparent_vol
|
|
647
|
+
* (
|
|
648
|
+
solution.get_amount(salt.cation, "mol") / salt.nu_cation
|
|
649
|
+
+ solution.get_amount(salt.anion, "mol") / salt.nu_anion
|
|
650
|
+
)
|
|
651
|
+
/ 2
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
pitzer_calc = True
|
|
655
|
+
|
|
656
|
+
logger.debug(f"Updated solution volume using Pitzer model for solute {salt.formula}")
|
|
657
|
+
|
|
658
|
+
# add the partial molar volume of any other solutes, except for water
|
|
659
|
+
# or the parent salt, which is already accounted for by the Pitzer parameters
|
|
660
|
+
for solute, mol in solution.components.items():
|
|
661
|
+
# ignore water
|
|
662
|
+
if solute in ["H2O", "HOH", "H2O(aq)"]:
|
|
663
|
+
continue
|
|
664
|
+
|
|
665
|
+
# ignore the salt cation and anion, if already accounted for by Pitzer
|
|
666
|
+
if pitzer_calc is True and solute in [salt.anion, salt.cation]:
|
|
667
|
+
continue
|
|
668
|
+
|
|
669
|
+
part_vol = solution.get_property(solute, "size.molar_volume")
|
|
670
|
+
if part_vol is not None:
|
|
671
|
+
solute_vol += part_vol * ureg.Quantity(mol, "mol")
|
|
672
|
+
logger.debug(f"Updated solution volume using direct partial molar volume for solute {solute}")
|
|
673
|
+
|
|
674
|
+
else:
|
|
675
|
+
logger.warning(
|
|
676
|
+
f"Volume of solute {solute} will be ignored because partial molar volume data are not available."
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
return solute_vol.to("L")
|
|
680
|
+
|
|
681
|
+
def equilibrate(
|
|
682
|
+
self,
|
|
683
|
+
solution: "solution.Solution",
|
|
684
|
+
atmosphere: bool = False,
|
|
685
|
+
solids: list[str] | None = None,
|
|
686
|
+
gases: dict[str, str | float] | None = None,
|
|
687
|
+
) -> None:
|
|
688
|
+
"""
|
|
689
|
+
Adjust the speciation of a Solution object to achieve chemical equilibrium.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
atmosphere:
|
|
693
|
+
Boolean indicating whether to equilibrate the solution
|
|
694
|
+
w.r.t atmospheric gases.
|
|
695
|
+
solids:
|
|
696
|
+
A list of solids used to achieve liquid-solid equilibrium. Each
|
|
697
|
+
solid in this list should be present in the Phreeqc database.
|
|
698
|
+
We assume a target saturation index of 0 and an infinite
|
|
699
|
+
amount of material.
|
|
700
|
+
gases:
|
|
701
|
+
A dictionary of gases used to achieve liquid-gas equilibrium.
|
|
702
|
+
Each key denotes the gas species, and the corresponding value
|
|
703
|
+
denotes its concentration, as a log partial pressure value or
|
|
704
|
+
other interpretable pressure units. For example, the following
|
|
705
|
+
are equivalent (log10(0.000316) = -3.5)
|
|
706
|
+
{"CO2": "0.000316 atm"}
|
|
707
|
+
{"CO2": -3.5}
|
|
708
|
+
"""
|
|
709
|
+
if self.ppsol is not None:
|
|
710
|
+
self.ppsol.forget()
|
|
711
|
+
self._setup_ppsol(solution)
|
|
712
|
+
|
|
713
|
+
# store the original solvent mass
|
|
714
|
+
orig_solvent_moles = solution.components[solution.solvent]
|
|
715
|
+
|
|
716
|
+
# store the original solvent elements and components with that element
|
|
717
|
+
# that we may need to add back later.
|
|
718
|
+
orig_el_dict = solution.get_el_amt_dict(nested=True)
|
|
719
|
+
orig_components_by_element = solution.get_components_by_element(nested=True)
|
|
720
|
+
|
|
721
|
+
# Use supplied gases, merged with atmospheric gases
|
|
722
|
+
# if atmosphere == True
|
|
723
|
+
gases = (ATMOSPHERE if atmosphere else {}) | (gases or {})
|
|
724
|
+
|
|
725
|
+
# Mapping from phase name to:
|
|
726
|
+
# (<saturation_index>, <amount_in_moles>) tuples (for solids).
|
|
727
|
+
# (<log_partial_pressure>, <amount_in_moles>) tuples (for gases).
|
|
728
|
+
phases = {}
|
|
729
|
+
if solids is not None:
|
|
730
|
+
# Assume saturation index of 0 for all solids.
|
|
731
|
+
phases |= dict.fromkeys(solids, (0, EQUILIBRIUM_PHASE_AMOUNT))
|
|
732
|
+
|
|
733
|
+
for k, v in gases.items():
|
|
734
|
+
v_quantity = ureg.Quantity(v)
|
|
735
|
+
if v_quantity.dimensionless:
|
|
736
|
+
log_partial_pressure = v_quantity.magnitude
|
|
737
|
+
else:
|
|
738
|
+
log_partial_pressure = math.log10(v_quantity.to("atm").magnitude)
|
|
739
|
+
phases |= {f"{k}(g)": (log_partial_pressure, EQUILIBRIUM_PHASE_AMOUNT)}
|
|
740
|
+
|
|
741
|
+
if phases:
|
|
742
|
+
phase_names = list(phases.keys())
|
|
743
|
+
saturation_indices = [v[0] for v in phases.values()]
|
|
744
|
+
amounts = [v[1] for v in phases.values()]
|
|
745
|
+
|
|
746
|
+
try:
|
|
747
|
+
self.ppsol.equalize(phases=phase_names, to_si=saturation_indices, in_phase=amounts)
|
|
748
|
+
except Exception as e:
|
|
749
|
+
# Re-raise exception due to unrecognized phases as ValueError.
|
|
750
|
+
if "Phase not found in database" in str(e):
|
|
751
|
+
raise ValueError(str(e))
|
|
752
|
+
raise e
|
|
753
|
+
|
|
754
|
+
solution.components = FormulaDict({})
|
|
755
|
+
# use the output from PHREEQC to update the Solution composition
|
|
756
|
+
# the .species_moles attribute should return MOLES (not moles per ___)
|
|
757
|
+
for s, mol in self.ppsol.species_moles.items():
|
|
758
|
+
solution.components[s] = mol
|
|
759
|
+
|
|
760
|
+
# log a message if any components were not touched by PHREEQC
|
|
761
|
+
# if that was the case, re-adjust the charge balance to account for those species (since PHREEQC did not)
|
|
762
|
+
missing_species = set(self._stored_comp.keys()) - {standardize_formula(s) for s in self.ppsol.species}
|
|
763
|
+
if len(missing_species) > 0:
|
|
764
|
+
logger.warning(
|
|
765
|
+
f"After equilibration, the amounts of species {sorted(missing_species)} were not modified "
|
|
766
|
+
"by PHREEQC. These species are likely absent from its database."
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
# tolerance (in moles) for detecting cases where an element amount
|
|
770
|
+
# is no longer balanced because of species that are not recognized
|
|
771
|
+
# by PHREEQC.
|
|
772
|
+
_atol = 1e-16
|
|
773
|
+
|
|
774
|
+
new_el_dict = solution.get_el_amt_dict(nested=True)
|
|
775
|
+
for el in orig_el_dict:
|
|
776
|
+
orig_el_amount = sum([orig_el_dict[el][k] for k in orig_el_dict[el]])
|
|
777
|
+
new_el_amount = sum([new_el_dict[el][k] for k in new_el_dict.get(el, [])])
|
|
778
|
+
|
|
779
|
+
# If this element went "missing", add back all components that
|
|
780
|
+
# contain this element (for any valence value)
|
|
781
|
+
if orig_el_amount - new_el_amount > _atol:
|
|
782
|
+
logger.info(
|
|
783
|
+
f"PHREEQC discarded element {el} during equilibration. Adding all components for this element."
|
|
784
|
+
)
|
|
785
|
+
solution.components.update(
|
|
786
|
+
{
|
|
787
|
+
component: self._stored_comp[component]
|
|
788
|
+
for components in orig_components_by_element[el].values()
|
|
789
|
+
for component in components
|
|
790
|
+
if component not in solution.components
|
|
791
|
+
}
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
# re-adjust charge balance for any missing species
|
|
795
|
+
# note that if balance_charge is set, it will have been passed to PHREEQC, so the only reason to re-adjust charge balance here is to account for any missing species.
|
|
796
|
+
solution._adjust_charge_balance()
|
|
797
|
+
|
|
798
|
+
# rescale the solvent mass to ensure the total mass of solution does not change
|
|
799
|
+
# this is important because PHREEQC and the pyEQL database may use slightly different molecular
|
|
800
|
+
# weights for water. Since water amount is passed to PHREEQC in kg but returned in moles, each
|
|
801
|
+
# call to equilibrate can thus result in a slight change in the Solution mass.
|
|
802
|
+
solution.components[solution.solvent] = orig_solvent_moles
|
|
803
|
+
|
|
804
|
+
def __deepcopy__(self, memo) -> "NativeEOS":
|
|
805
|
+
# custom deepcopy required because the PhreeqPython instance used by the Native and Phreeqc engines
|
|
806
|
+
# is not pickle-able.
|
|
807
|
+
|
|
808
|
+
cls = self.__class__
|
|
809
|
+
result = cls.__new__(cls)
|
|
810
|
+
memo[id(self)] = result
|
|
811
|
+
for k, v in self.__dict__.items():
|
|
812
|
+
if k == "pp":
|
|
813
|
+
result.pp = PhreeqPython(database=self.phreeqc_db, database_directory=self.db_path)
|
|
814
|
+
continue
|
|
815
|
+
setattr(result, k, copy.deepcopy(v, memo))
|
|
816
|
+
return result
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class PhreeqcEOS(NativeEOS):
|
|
820
|
+
"""Engine based on the PhreeqC model, as implemented via the phreeqpython package."""
|
|
821
|
+
|
|
822
|
+
def __init__(
|
|
823
|
+
self,
|
|
824
|
+
phreeqc_db: Literal[
|
|
825
|
+
"phreeqc.dat", "vitens.dat", "wateq4f_PWN.dat", "pitzer.dat", "llnl.dat", "geothermal.dat"
|
|
826
|
+
] = "phreeqc.dat",
|
|
827
|
+
) -> None:
|
|
828
|
+
"""
|
|
829
|
+
Args:
|
|
830
|
+
phreeqc_db: Name of the PHREEQC database file to use for solution thermodynamics
|
|
831
|
+
and speciation calculations. Generally speaking, `llnl.dat` is recommended
|
|
832
|
+
for moderate salinity water and prediction of mineral solubilities,
|
|
833
|
+
`wateq4f_PWN.dat` is recommended for low to moderate salinity waters. It is
|
|
834
|
+
similar to vitens.dat but has many more species. `pitzer.dat` is recommended
|
|
835
|
+
when accurate activity coefficients in solutions above 1 M TDS are desired, but
|
|
836
|
+
it has fewer species than the other databases. `llnl.dat` and `geothermal.dat`
|
|
837
|
+
may offer improved prediction of LSI but currently these databases are not
|
|
838
|
+
usable because they do not allow for conductivity calculations.
|
|
839
|
+
"""
|
|
840
|
+
super().__init__(phreeqc_db=phreeqc_db)
|
|
841
|
+
|
|
842
|
+
def get_activity_coefficient(self, solution: "solution.Solution", solute: str) -> ureg.Quantity:
|
|
843
|
+
"""
|
|
844
|
+
Return the *molal scale* activity coefficient of solute, given a Solution
|
|
845
|
+
object.
|
|
846
|
+
"""
|
|
847
|
+
if (self.ppsol is None) or (solution.components != self._stored_comp):
|
|
848
|
+
self._destroy_ppsol()
|
|
849
|
+
self._setup_ppsol(solution)
|
|
850
|
+
|
|
851
|
+
# translate the species into keys that phreeqc will understand
|
|
852
|
+
k = standardize_formula(solute)
|
|
853
|
+
spl = k.split("[")
|
|
854
|
+
el = spl[0]
|
|
855
|
+
chg = spl[1].split("]")[0]
|
|
856
|
+
if chg[-1] == "1":
|
|
857
|
+
chg = chg[0] # just pass + or -, not +1 / -1
|
|
858
|
+
k = el + chg
|
|
859
|
+
|
|
860
|
+
# calculate the molal scale activity coefficient
|
|
861
|
+
# act = self.ppsol.activity(k, "mol") / self.ppsol.molality(k, "mol")
|
|
862
|
+
act = self.ppsol.pp.ip.get_activity(self.ppsol.number, k) / self.ppsol.pp.ip.get_molality(self.ppsol.number, k)
|
|
863
|
+
|
|
864
|
+
return ureg.Quantity(act, "dimensionless")
|
|
865
|
+
|
|
866
|
+
def get_osmotic_coefficient(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
867
|
+
"""
|
|
868
|
+
Return the *molal scale* osmotic coefficient of solute, given a Solution
|
|
869
|
+
object.
|
|
870
|
+
|
|
871
|
+
PHREEQC appears to assume a unit osmotic coefficient unless the pitzer database
|
|
872
|
+
is used. Unfortunately, there is no easy way to access the osmotic coefficient
|
|
873
|
+
via phreeqcpython
|
|
874
|
+
"""
|
|
875
|
+
# TODO - find a way to access or calculate osmotic coefficient
|
|
876
|
+
return ureg.Quantity(1, "dimensionless")
|
|
877
|
+
|
|
878
|
+
def get_solute_volume(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
879
|
+
"""Return the volume of the solutes."""
|
|
880
|
+
# TODO - phreeqc seems to have no concept of volume, but it does calculate density
|
|
881
|
+
return ureg.Quantity(0, "L")
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
class Phreeqc2026EOS(EOS):
|
|
885
|
+
"""Engine based on the PhreeqC model, as implemented in the pyphreeqc
|
|
886
|
+
module of pyEQL."""
|
|
887
|
+
|
|
888
|
+
def __init__(
|
|
889
|
+
self,
|
|
890
|
+
phreeqc_db: Literal[
|
|
891
|
+
"phreeqc.dat", "vitens.dat", "wateq4f_PWN.dat", "pitzer.dat", "llnl.dat", "geothermal.dat"
|
|
892
|
+
] = "phreeqc.dat",
|
|
893
|
+
) -> None:
|
|
894
|
+
"""
|
|
895
|
+
Args:
|
|
896
|
+
phreeqc_db: Name of the PHREEQC database file to use for solution thermodynamics
|
|
897
|
+
and speciation calculations. Generally speaking, `llnl.dat` is recommended
|
|
898
|
+
for moderate salinity water and prediction of mineral solubilities,
|
|
899
|
+
`wateq4f_PWN.dat` is recommended for low to moderate salinity waters. It is
|
|
900
|
+
similar to vitens.dat but has many more species. `pitzer.dat` is recommended
|
|
901
|
+
when accurate activity coefficients in solutions above 1 M TDS are desired, but
|
|
902
|
+
it has fewer species than the other databases. `llnl.dat` and `geothermal.dat`
|
|
903
|
+
may offer improved prediction of LSI but currently these databases are not
|
|
904
|
+
usable because they do not allow for conductivity calculations.
|
|
905
|
+
"""
|
|
906
|
+
|
|
907
|
+
from pyEQL.phreeqc import IS_AVAILABLE, Phreeqc # noqa: PLC0415
|
|
908
|
+
|
|
909
|
+
if not IS_AVAILABLE:
|
|
910
|
+
raise RuntimeError("pyEQL phreeqc support is not available in this installation")
|
|
911
|
+
|
|
912
|
+
self.phreeqc_db = phreeqc_db
|
|
913
|
+
# database files in this list are not distributed with phreeqpython
|
|
914
|
+
self.db_path = (
|
|
915
|
+
Path(os.path.dirname(__file__)) / "database" if self.phreeqc_db in ["llnl.dat", "geothermal.dat"] else None
|
|
916
|
+
)
|
|
917
|
+
# create the PhreeqcPython instance
|
|
918
|
+
self.pp = Phreeqc(database=self.phreeqc_db, database_directory=self.db_path)
|
|
919
|
+
# attributes to hold the PhreeqPython solution.
|
|
920
|
+
self.ppsol = None
|
|
921
|
+
# store the solution composition to see whether we need to re-instantiate the solution
|
|
922
|
+
self._stored_comp = None
|
|
923
|
+
|
|
924
|
+
def _setup_ppsol(self, solution: "solution.Solution") -> None:
|
|
925
|
+
"""Helper method to set up a PhreeqPython solution for subsequent analysis."""
|
|
926
|
+
|
|
927
|
+
from pyEQL.phreeqc import PHRQSol # noqa: PLC0415
|
|
928
|
+
|
|
929
|
+
self._stored_comp = solution.components.copy()
|
|
930
|
+
solv_mass = solution.solvent_mass.to("kg").magnitude
|
|
931
|
+
# inherit bulk solution properties
|
|
932
|
+
d = {
|
|
933
|
+
"temp": solution.temperature.to("degC").magnitude,
|
|
934
|
+
"units": "mol/kgw", # to avoid confusion about volume, use mol/kgw which seems more robust in PHREEQC
|
|
935
|
+
"pH": solution.pH,
|
|
936
|
+
"pe": solution.pE,
|
|
937
|
+
"redox": "pe", # hard-coded to use the pe
|
|
938
|
+
# PHREEQC will assume 1 kg if not specified, there is also no direct way to specify volume, so we
|
|
939
|
+
# really have to specify the solvent mass in 1 liter of solution
|
|
940
|
+
"water": solv_mass,
|
|
941
|
+
}
|
|
942
|
+
if solution.balance_charge == "pH":
|
|
943
|
+
d["pH"] = str(d["pH"]) + " charge"
|
|
944
|
+
if solution.balance_charge == "pE":
|
|
945
|
+
d["pe"] = str(d["pe"]) + " charge"
|
|
946
|
+
|
|
947
|
+
# add the composition to the dict
|
|
948
|
+
# also, skip H and O
|
|
949
|
+
for el, mol in solution.get_el_amt_dict().items():
|
|
950
|
+
# CAUTION - care must be taken to avoid unintended behavior here. get_el_amt_dict() will return
|
|
951
|
+
# all distinct oxi states of each element present. If there are elements present whose oxi states
|
|
952
|
+
# are NOT recognized by PHREEQC (via SPECIAL_ELEMENTS) then the amount of only 1 oxi state will be
|
|
953
|
+
# entered into the composition dict. This can especially cause problems after equilibrate() has already
|
|
954
|
+
# been called once. For example, equilibrating a simple NaCl solution generates Cl species that are assigned
|
|
955
|
+
# various oxidations states, -1 mostly, but also 1, 2, and 3. Since the concentrations of everything
|
|
956
|
+
# except the -1 oxi state are tiny, this can result in Cl "disappearing" from the solution if
|
|
957
|
+
# equlibrate is called again. It also causes non-determinism, because the amount is taken from whatever
|
|
958
|
+
# oxi state happens to be iterated through last.
|
|
959
|
+
|
|
960
|
+
# strip off the oxi state
|
|
961
|
+
bare_el = el.split("(")[0]
|
|
962
|
+
if bare_el in SPECIAL_ELEMENTS:
|
|
963
|
+
# PHREEQC will ignore float-formatted oxi states. Need to make sure we are
|
|
964
|
+
# passing, e.g. 'C(4)' and not 'C(4.0)'
|
|
965
|
+
key = f"{bare_el}({int(float(el.split('(')[-1].split(')')[0]))})"
|
|
966
|
+
elif bare_el in ["H", "O"]:
|
|
967
|
+
continue
|
|
968
|
+
else:
|
|
969
|
+
key = bare_el
|
|
970
|
+
|
|
971
|
+
if key in d:
|
|
972
|
+
# when multiple oxi states for the same (non-SPECIAL) element are present, make sure to
|
|
973
|
+
# add all their amounts together
|
|
974
|
+
d[key] += str(mol / solv_mass)
|
|
975
|
+
else:
|
|
976
|
+
d[key] = str(mol / solv_mass)
|
|
977
|
+
|
|
978
|
+
# tell PHREEQC which species to use for charge balance
|
|
979
|
+
if solution.balance_charge is not None and solution._cb_species in solution.get_components_by_element()[el]:
|
|
980
|
+
d[key] += " charge"
|
|
981
|
+
|
|
982
|
+
try:
|
|
983
|
+
ppsol = self.pp.add_solution(PHRQSol(d))
|
|
984
|
+
except Exception as e:
|
|
985
|
+
# catch problems with the input to phreeqc
|
|
986
|
+
raise ValueError(
|
|
987
|
+
"There is a problem with your input. The error message received from "
|
|
988
|
+
f" phreeqc is:\n\n {e}\n Check your input arguments, especially "
|
|
989
|
+
"the composition dictionary, and try again."
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
self.ppsol = ppsol
|
|
993
|
+
|
|
994
|
+
def _destroy_ppsol(self) -> None:
|
|
995
|
+
if self.ppsol is not None:
|
|
996
|
+
self.pp.remove_solution(0) # TODO: Are we only expecting a single solution per wrapper?
|
|
997
|
+
self.ppsol = None
|
|
998
|
+
|
|
999
|
+
def get_activity_coefficient(self, solution: "solution.Solution", solute: str) -> ureg.Quantity:
|
|
1000
|
+
"""
|
|
1001
|
+
Return the *molal scale* activity coefficient of solute, given a Solution
|
|
1002
|
+
object.
|
|
1003
|
+
"""
|
|
1004
|
+
if (self.ppsol is None) or (solution.components != self._stored_comp):
|
|
1005
|
+
self._destroy_ppsol()
|
|
1006
|
+
self._setup_ppsol(solution)
|
|
1007
|
+
|
|
1008
|
+
# translate the species into keys that phreeqc will understand
|
|
1009
|
+
k = standardize_formula(solute)
|
|
1010
|
+
spl = k.split("[")
|
|
1011
|
+
el = spl[0]
|
|
1012
|
+
chg = spl[1].split("]")[0]
|
|
1013
|
+
if chg[-1] == "1":
|
|
1014
|
+
chg = chg[0] # just pass + or -, not +1 / -1
|
|
1015
|
+
k = el + chg
|
|
1016
|
+
|
|
1017
|
+
# calculate the molal scale activity coefficient
|
|
1018
|
+
# act = self.ppsol.activity(k, "mol") / self.ppsol.molality(k, "mol")
|
|
1019
|
+
act = self.ppsol.get_activity(k) / self.ppsol.get_molality(k)
|
|
1020
|
+
|
|
1021
|
+
return ureg.Quantity(act, "dimensionless")
|
|
1022
|
+
|
|
1023
|
+
def get_osmotic_coefficient(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
1024
|
+
osmotic = self.ppsol.get_osmotic_coefficient()
|
|
1025
|
+
return ureg.Quantity(osmotic, "dimensionless")
|
|
1026
|
+
|
|
1027
|
+
def get_solute_volume(self, solution: "solution.Solution") -> ureg.Quantity:
|
|
1028
|
+
"""Return the volume of the solutes."""
|
|
1029
|
+
# TODO - phreeqc seems to have no concept of volume, but it does calculate density
|
|
1030
|
+
return ureg.Quantity(0, "L")
|
|
1031
|
+
|
|
1032
|
+
def equilibrate(
|
|
1033
|
+
self,
|
|
1034
|
+
solution: "solution.Solution",
|
|
1035
|
+
atmosphere: bool = False,
|
|
1036
|
+
solids: list[str] | None = None,
|
|
1037
|
+
gases: dict[str, str | float] | None = None,
|
|
1038
|
+
) -> None:
|
|
1039
|
+
"""
|
|
1040
|
+
Adjust the speciation of a Solution object to achieve chemical equilibrium.
|
|
1041
|
+
|
|
1042
|
+
Args:
|
|
1043
|
+
atmosphere:
|
|
1044
|
+
Boolean indicating whether to equilibrate the solution
|
|
1045
|
+
w.r.t atmospheric gases.
|
|
1046
|
+
solids:
|
|
1047
|
+
A list of solids used to achieve liquid-solid equilibrium. Each
|
|
1048
|
+
solid in this list should be present in the Phreeqc database.
|
|
1049
|
+
We assume a target saturation index of 0 and an infinite
|
|
1050
|
+
amount of material.
|
|
1051
|
+
gases:
|
|
1052
|
+
A dictionary of gases used to achieve liquid-gas equilibrium.
|
|
1053
|
+
Each key denotes the gas species, and the corresponding value
|
|
1054
|
+
denotes its concentration, as a log partial pressure value or
|
|
1055
|
+
other interpretable pressure units. For example, the following
|
|
1056
|
+
are equivalent (log10(0.000316) = -3.5)
|
|
1057
|
+
{"CO2": "0.000316 atm"}
|
|
1058
|
+
{"CO2": -3.5}
|
|
1059
|
+
"""
|
|
1060
|
+
if self.ppsol is not None:
|
|
1061
|
+
self.ppsol.forget()
|
|
1062
|
+
self._setup_ppsol(solution)
|
|
1063
|
+
|
|
1064
|
+
# store the original solvent mass
|
|
1065
|
+
orig_solvent_moles = solution.components[solution.solvent]
|
|
1066
|
+
|
|
1067
|
+
# store the original solvent elements and components with that element
|
|
1068
|
+
# that we may need to add back later.
|
|
1069
|
+
orig_el_dict = solution.get_el_amt_dict(nested=True)
|
|
1070
|
+
orig_components_by_element = solution.get_components_by_element(nested=True)
|
|
1071
|
+
|
|
1072
|
+
# Use supplied gases, merged with atmospheric gases
|
|
1073
|
+
# if atmosphere == True
|
|
1074
|
+
gases = (ATMOSPHERE if atmosphere else {}) | (gases or {})
|
|
1075
|
+
|
|
1076
|
+
# Mapping from phase name to:
|
|
1077
|
+
# (<saturation_index>, <amount_in_moles>) tuples (for solids).
|
|
1078
|
+
# (<log_partial_pressure>, <amount_in_moles>) tuples (for gases).
|
|
1079
|
+
phases = {}
|
|
1080
|
+
if solids is not None:
|
|
1081
|
+
# Assume saturation index of 0 for all solids.
|
|
1082
|
+
phases |= dict.fromkeys(solids, (0, EQUILIBRIUM_PHASE_AMOUNT))
|
|
1083
|
+
|
|
1084
|
+
for k, v in gases.items():
|
|
1085
|
+
v_quantity = ureg.Quantity(v)
|
|
1086
|
+
if v_quantity.dimensionless:
|
|
1087
|
+
log_partial_pressure = v_quantity.magnitude
|
|
1088
|
+
else:
|
|
1089
|
+
log_partial_pressure = math.log10(v_quantity.to("atm").magnitude)
|
|
1090
|
+
phases |= {f"{k}(g)": (log_partial_pressure, EQUILIBRIUM_PHASE_AMOUNT)}
|
|
1091
|
+
|
|
1092
|
+
if phases:
|
|
1093
|
+
phase_names = list(phases.keys())
|
|
1094
|
+
saturation_indices = [v[0] for v in phases.values()]
|
|
1095
|
+
amounts = [v[1] for v in phases.values()]
|
|
1096
|
+
|
|
1097
|
+
try:
|
|
1098
|
+
self.ppsol.equalize(phases=phase_names, saturation_indices=saturation_indices, amounts=amounts)
|
|
1099
|
+
except Exception as e:
|
|
1100
|
+
# Re-raise exception due to unrecognized phases as ValueError.
|
|
1101
|
+
if "Phase not found in database" in str(e):
|
|
1102
|
+
raise ValueError(str(e))
|
|
1103
|
+
raise e
|
|
1104
|
+
|
|
1105
|
+
solution.components = FormulaDict({})
|
|
1106
|
+
# use the output from PHREEQC to update the Solution composition
|
|
1107
|
+
# the .species_moles attribute should return MOLES (not moles per ___)
|
|
1108
|
+
for s, mol in self.ppsol.species_moles.items():
|
|
1109
|
+
solution.components[s] = mol
|
|
1110
|
+
|
|
1111
|
+
# log a message if any components were not touched by PHREEQC
|
|
1112
|
+
# if that was the case, re-adjust the charge balance to account for those species (since PHREEQC did not)
|
|
1113
|
+
missing_species = set(self._stored_comp.keys()) - {standardize_formula(s) for s in self.ppsol.species}
|
|
1114
|
+
if len(missing_species) > 0:
|
|
1115
|
+
logger.warning(
|
|
1116
|
+
f"After equilibration, the amounts of species {sorted(missing_species)} were not modified "
|
|
1117
|
+
"by PHREEQC. These species are likely absent from its database."
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
# tolerance (in moles) for detecting cases where an element amount
|
|
1121
|
+
# is no longer balanced because of species that are not recognized
|
|
1122
|
+
# by PHREEQC.
|
|
1123
|
+
_atol = 1e-16
|
|
1124
|
+
|
|
1125
|
+
new_el_dict = solution.get_el_amt_dict(nested=True)
|
|
1126
|
+
for el in orig_el_dict:
|
|
1127
|
+
orig_el_amount = sum([orig_el_dict[el][k] for k in orig_el_dict[el]])
|
|
1128
|
+
new_el_amount = sum([new_el_dict[el][k] for k in new_el_dict.get(el, [])])
|
|
1129
|
+
|
|
1130
|
+
# If this element went "missing", add back all components that
|
|
1131
|
+
# contain this element (for any valence value)
|
|
1132
|
+
if orig_el_amount - new_el_amount > _atol:
|
|
1133
|
+
logger.info(
|
|
1134
|
+
f"PHREEQC discarded element {el} during equilibration. Adding all components for this element."
|
|
1135
|
+
)
|
|
1136
|
+
solution.components.update(
|
|
1137
|
+
{
|
|
1138
|
+
component: self._stored_comp[component]
|
|
1139
|
+
for components in orig_components_by_element[el].values()
|
|
1140
|
+
for component in components
|
|
1141
|
+
if component not in solution.components
|
|
1142
|
+
}
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
# re-adjust charge balance for any missing species
|
|
1146
|
+
# note that if balance_charge is set, it will have been passed to PHREEQC, so the only reason to re-adjust charge balance here is to account for any missing species.
|
|
1147
|
+
solution._adjust_charge_balance()
|
|
1148
|
+
|
|
1149
|
+
# rescale the solvent mass to ensure the total mass of solution does not change
|
|
1150
|
+
# this is important because PHREEQC and the pyEQL database may use slightly different molecular
|
|
1151
|
+
# weights for water. Since water amount is passed to PHREEQC in kg but returned in moles, each
|
|
1152
|
+
# call to equilibrate can thus result in a slight change in the Solution mass.
|
|
1153
|
+
solution.components[solution.solvent] = orig_solvent_moles
|