pyEQL 1.4.0rc9__cp312-cp312-win_amd64.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.
Files changed (519) hide show
  1. pyEQL/__init__.py +50 -0
  2. pyEQL/_phreeqc.cp312-win_amd64.pyd +0 -0
  3. pyEQL/activity_correction.py +879 -0
  4. pyEQL/database/geothermal.dat +5693 -0
  5. pyEQL/database/llnl.dat +19305 -0
  6. pyEQL/database/phreeqc_license.txt +54 -0
  7. pyEQL/database/pyeql_db.json +35607 -0
  8. pyEQL/engines.py +1153 -0
  9. pyEQL/equilibrium.py +227 -0
  10. pyEQL/functions.py +281 -0
  11. pyEQL/phreeqc/__init__.py +5 -0
  12. pyEQL/phreeqc/bindings.cpp +84 -0
  13. pyEQL/phreeqc/core.py +239 -0
  14. pyEQL/phreeqc/database/Amm.dat +1968 -0
  15. pyEQL/phreeqc/database/CMakeLists.txt +32 -0
  16. pyEQL/phreeqc/database/ColdChem.dat +267 -0
  17. pyEQL/phreeqc/database/Concrete_PHR.dat +158 -0
  18. pyEQL/phreeqc/database/Concrete_PZ.dat +195 -0
  19. pyEQL/phreeqc/database/Kinec.v2.dat +12039 -0
  20. pyEQL/phreeqc/database/Kinec_v3.dat +12159 -0
  21. pyEQL/phreeqc/database/Makefile.am +28 -0
  22. pyEQL/phreeqc/database/Makefile.in +530 -0
  23. pyEQL/phreeqc/database/PHREEQC_ThermoddemV1.10_15Dec2020.dat +12965 -0
  24. pyEQL/phreeqc/database/Tipping_Hurley.dat +4137 -0
  25. pyEQL/phreeqc/database/__init__.py +0 -0
  26. pyEQL/phreeqc/database/core10.dat +6824 -0
  27. pyEQL/phreeqc/database/frezchem.dat +634 -0
  28. pyEQL/phreeqc/database/iso.dat +7235 -0
  29. pyEQL/phreeqc/database/llnl.dat +19310 -0
  30. pyEQL/phreeqc/database/minteq.dat +5654 -0
  31. pyEQL/phreeqc/database/minteq.v4.dat +13212 -0
  32. pyEQL/phreeqc/database/phreeqc.dat +1972 -0
  33. pyEQL/phreeqc/database/phreeqc_rates.dat +3158 -0
  34. pyEQL/phreeqc/database/pitzer.dat +1044 -0
  35. pyEQL/phreeqc/database/sit.dat +14348 -0
  36. pyEQL/phreeqc/database/wateq4f.dat +4036 -0
  37. pyEQL/phreeqc/ext/README.md +10 -0
  38. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/CMakeLists.txt +476 -0
  39. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/INSTALL +302 -0
  40. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/IPhreeqc.rc +61 -0
  41. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/IPhreeqcConfig.cmake.in +4 -0
  42. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/Makefile.am +8 -0
  43. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/Makefile.in +816 -0
  44. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/aclocal.m4 +1217 -0
  45. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/ALL_BUILD.vcxproj +185 -0
  46. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/ALL_BUILD.vcxproj.filters +8 -0
  47. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/generate.stamp +1 -0
  48. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CMakeFiles/generate.stamp.depend +79 -0
  49. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/CTestTestfile.cmake +6 -0
  50. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/Continuous.vcxproj +240 -0
  51. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/Continuous.vcxproj.filters +17 -0
  52. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/DartConfiguration.tcl +109 -0
  53. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/Experimental.vcxproj +240 -0
  54. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/Experimental.vcxproj.filters +17 -0
  55. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/INSTALL.vcxproj +209 -0
  56. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/INSTALL.vcxproj.filters +13 -0
  57. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/CSelectedOutput.obj +0 -0
  58. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Dictionary.obj +0 -0
  59. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/ExchComp.obj +0 -0
  60. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Exchange.obj +0 -0
  61. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/GasComp.obj +0 -0
  62. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/GasPhase.obj +0 -0
  63. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.lib.recipe +11 -0
  64. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.obj +0 -0
  65. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/CL.command.1.tlog +0 -0
  66. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/CL.read.1.tlog +0 -0
  67. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/CL.write.1.tlog +0 -0
  68. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/Cl.items.tlog +82 -0
  69. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/CustomBuild.command.1.tlog +10 -0
  70. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/CustomBuild.read.1.tlog +78 -0
  71. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/CustomBuild.write.1.tlog +2 -0
  72. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/IPhreeqc.lastbuildstate +2 -0
  73. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/Lib-link.read.1.tlog +0 -0
  74. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/Lib-link.write.1.tlog +0 -0
  75. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc.tlog/Lib.command.1.tlog +0 -0
  76. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqcLib.obj +0 -0
  77. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/IPhreeqc_interface_F.obj +0 -0
  78. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/ISolution.obj +0 -0
  79. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/ISolutionComp.obj +0 -0
  80. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Keywords.obj +0 -0
  81. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/KineticsComp.obj +0 -0
  82. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/NameDouble.obj +0 -0
  83. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/NumKeyword.obj +0 -0
  84. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/PBasic.obj +0 -0
  85. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/PHRQ_base.obj +0 -0
  86. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/PHRQ_io.obj +0 -0
  87. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/PHRQ_io_output.obj +0 -0
  88. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/PPassemblage.obj +0 -0
  89. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/PPassemblageComp.obj +0 -0
  90. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Parser.obj +0 -0
  91. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Phreeqc.obj +0 -0
  92. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Pressure.obj +0 -0
  93. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Reaction.obj +0 -0
  94. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/ReadClass.obj +0 -0
  95. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/SS.obj +0 -0
  96. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/SSassemblage.obj +0 -0
  97. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/SScomp.obj +0 -0
  98. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/SelectedOutput.obj +0 -0
  99. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Serializer.obj +0 -0
  100. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Solution.obj +0 -0
  101. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/SolutionIsotope.obj +0 -0
  102. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/StorageBin.obj +0 -0
  103. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/StorageBinList.obj +0 -0
  104. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Surface.obj +0 -0
  105. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/SurfaceCharge.obj +0 -0
  106. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/SurfaceComp.obj +0 -0
  107. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/System.obj +0 -0
  108. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Temperature.obj +0 -0
  109. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Use.obj +0 -0
  110. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/UserPunch.obj +0 -0
  111. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Utils.obj +0 -0
  112. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/Var.obj +0 -0
  113. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/advection.obj +0 -0
  114. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/basicsubs.obj +0 -0
  115. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/cl1.obj +0 -0
  116. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/cvdense.obj +0 -0
  117. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/cvode.obj +0 -0
  118. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/cxxKinetics.obj +0 -0
  119. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/cxxMix.obj +0 -0
  120. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/dense.obj +0 -0
  121. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/dumper.obj +0 -0
  122. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/gases.obj +0 -0
  123. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/input.obj +0 -0
  124. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/integrate.obj +0 -0
  125. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/inverse.obj +0 -0
  126. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/isotopes.obj +0 -0
  127. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/kinetics.obj +0 -0
  128. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/mainsubs.obj +0 -0
  129. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/model.obj +0 -0
  130. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/nvector.obj +0 -0
  131. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/nvector_serial.obj +0 -0
  132. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/parse.obj +0 -0
  133. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/phqalloc.obj +0 -0
  134. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/pitzer.obj +0 -0
  135. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/pitzer_structures.obj +0 -0
  136. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/prep.obj +0 -0
  137. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/print.obj +0 -0
  138. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/read.obj +0 -0
  139. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/readtr.obj +0 -0
  140. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/runner.obj +0 -0
  141. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/sit.obj +0 -0
  142. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/smalldense.obj +0 -0
  143. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/spread.obj +0 -0
  144. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/step.obj +0 -0
  145. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/structures.obj +0 -0
  146. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/sundialsmath.obj +0 -0
  147. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/tally.obj +0 -0
  148. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/tidy.obj +0 -0
  149. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/transport.obj +0 -0
  150. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.dir/Release/utilities.obj +0 -0
  151. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.sln +116 -0
  152. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.vcxproj +443 -0
  153. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/IPhreeqc.vcxproj.filters +456 -0
  154. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/Nightly.vcxproj +240 -0
  155. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/Nightly.vcxproj.filters +17 -0
  156. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/NightlyMemoryCheck.vcxproj +240 -0
  157. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/NightlyMemoryCheck.vcxproj.filters +17 -0
  158. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/Release/IPhreeqc.lib +0 -0
  159. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/build/cmake_install.cmake +40 -0
  160. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/ar-lib +270 -0
  161. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/compile +347 -0
  162. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/config.guess +1441 -0
  163. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/config.sub +1813 -0
  164. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/depcomp +791 -0
  165. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/install-sh +508 -0
  166. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/ltmain.sh +11156 -0
  167. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/missing +215 -0
  168. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/config/test-driver +148 -0
  169. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/configure +23867 -0
  170. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/configure.ac +136 -0
  171. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Amm.dat +1968 -0
  172. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/CMakeLists.txt +32 -0
  173. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/ColdChem.dat +267 -0
  174. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Concrete_PHR.dat +158 -0
  175. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Concrete_PZ.dat +195 -0
  176. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Kinec.v2.dat +12039 -0
  177. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Kinec_v3.dat +12159 -0
  178. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Makefile.am +28 -0
  179. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Makefile.in +530 -0
  180. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/PHREEQC_ThermoddemV1.10_15Dec2020.dat +12965 -0
  181. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/Tipping_Hurley.dat +4137 -0
  182. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/core10.dat +6824 -0
  183. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/frezchem.dat +634 -0
  184. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/iso.dat +7235 -0
  185. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/llnl.dat +19310 -0
  186. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/minteq.dat +5654 -0
  187. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/minteq.v4.dat +13212 -0
  188. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/phreeqc.dat +1972 -0
  189. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/phreeqc_rates.dat +3158 -0
  190. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/pitzer.dat +1044 -0
  191. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/sit.dat +14348 -0
  192. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/database/wateq4f.dat +4036 -0
  193. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/CMakeLists.txt +35 -0
  194. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/IPhreeqc.pdf +0 -0
  195. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Makefile.am +24 -0
  196. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Makefile.in +545 -0
  197. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/NOTICE +51 -0
  198. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Phreeqc_2_1999_manual.pdf +0 -0
  199. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/Phreeqc_3_2013_manual.pdf +0 -0
  200. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/README +428 -0
  201. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/RELEASE +7294 -0
  202. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8h.html +5096 -0
  203. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8h_source.html +389 -0
  204. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8hpp.html +83 -0
  205. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/IPhreeqc_8hpp_source.html +478 -0
  206. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/Var_8h.html +318 -0
  207. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/Var_8h_source.html +200 -0
  208. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/bc_s.png +0 -0
  209. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/bdwn.png +0 -0
  210. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqc.html +2274 -0
  211. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqc.png +0 -0
  212. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqcStop.html +69 -0
  213. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/classIPhreeqcStop.png +0 -0
  214. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/closed.png +0 -0
  215. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/dir_68267d1309a1af8e8297ef4c3efbcdba.html +68 -0
  216. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/doxygen.css +1440 -0
  217. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/doxygen.png +0 -0
  218. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/dynsections.js +97 -0
  219. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2blank.png +0 -0
  220. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2doc.png +0 -0
  221. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2folderclosed.png +0 -0
  222. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2folderopen.png +0 -0
  223. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2lastnode.png +0 -0
  224. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2link.png +0 -0
  225. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2mlastnode.png +0 -0
  226. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2mnode.png +0 -0
  227. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2node.png +0 -0
  228. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2plastnode.png +0 -0
  229. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2pnode.png +0 -0
  230. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2splitbar.png +0 -0
  231. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/ftv2vertline.png +0 -0
  232. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/index.html +58 -0
  233. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/jquery.js +31 -0
  234. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/nav_f.png +0 -0
  235. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/nav_g.png +0 -0
  236. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/nav_h.png +0 -0
  237. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/open.png +0 -0
  238. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/structVAR.html +143 -0
  239. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/sync_off.png +0 -0
  240. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/sync_on.png +0 -0
  241. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_a.png +0 -0
  242. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_b.png +0 -0
  243. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_h.png +0 -0
  244. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tab_s.png +0 -0
  245. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/html/tabs.css +60 -0
  246. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/doc/phreeqc3.chm +0 -0
  247. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/CMakeLists.txt +11 -0
  248. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/Makefile.am +88 -0
  249. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/Makefile.in +696 -0
  250. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/CMakeLists.txt +1 -0
  251. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/CMakeLists.txt +35 -0
  252. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/CMakeLists.txt.in +21 -0
  253. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/README.txt +44 -0
  254. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/advect.c +101 -0
  255. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/ic +17 -0
  256. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/c/advect/phreeqc.dat +1579 -0
  257. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/CMakeLists.txt +10 -0
  258. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/README.txt +3 -0
  259. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/CMakeLists.txt +9 -0
  260. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/phreeqc.dat +1582 -0
  261. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/runphreeqc.xls +0 -0
  262. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/excel/withcallback.xls +0 -0
  263. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/CMakeLists.txt +11 -0
  264. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/Gypsum.py +52 -0
  265. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/parallel_advect.py +465 -0
  266. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/phreeqc.dat +1582 -0
  267. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/pitzer.dat +790 -0
  268. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/com/python/wateq4f.dat +3846 -0
  269. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/CMakeLists.txt +1 -0
  270. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/CMakeLists.txt +35 -0
  271. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/CMakeLists.txt.in +20 -0
  272. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/README.txt +45 -0
  273. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/advect.cpp +110 -0
  274. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/ic +17 -0
  275. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/cpp/advect/phreeqc.dat +1579 -0
  276. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/CMakeLists.txt +1 -0
  277. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/CMakeLists.txt +44 -0
  278. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/CMakeLists.txt.in +24 -0
  279. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/README.txt +45 -0
  280. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/advect.F90 +102 -0
  281. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/ic +17 -0
  282. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/fortran/advect/phreeqc.dat +1579 -0
  283. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/CMakeLists.txt +26 -0
  284. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/CMakeLists.txt.in +20 -0
  285. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/README.txt +37 -0
  286. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/ex2 +26 -0
  287. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/main.cpp +20 -0
  288. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/phreeqc.dat +1837 -0
  289. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/examples/using-cmake/post-install.cmake.in +7 -0
  290. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/CMakeLists.txt +185 -0
  291. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/FileTest.cpp +171 -0
  292. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/FileTest.h +34 -0
  293. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/Makefile.am +18 -0
  294. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/Makefile.in +466 -0
  295. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestCVar.cpp +9 -0
  296. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestIPhreeqc.cpp +4901 -0
  297. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestIPhreeqcLib.cpp +4644 -0
  298. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestSelectedOutput.cpp +669 -0
  299. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/TestVar.cpp +10 -0
  300. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/conv_fail.in +11 -0
  301. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/dump +42 -0
  302. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/iso.dat +7231 -0
  303. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/kinn20140218 +349 -0
  304. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/missing_e.dat +1556 -0
  305. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/multi_punch +105 -0
  306. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/multi_punch_no_set +102 -0
  307. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/phreeqc.dat.90a6449 +1935 -0
  308. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/gtest/phreeqc.dat.old +1556 -0
  309. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/libtool.m4 +8388 -0
  310. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/ltoptions.m4 +437 -0
  311. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/ltsugar.m4 +124 -0
  312. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/ltversion.m4 +23 -0
  313. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/m4/lt~obsolete.m4 +99 -0
  314. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/resource.h +14 -0
  315. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/CSelectedOutput.cpp +401 -0
  316. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/CSelectedOutput.hxx +77 -0
  317. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/CVar.hxx +162 -0
  318. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Debug.h +12 -0
  319. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/ErrorReporter.hxx +70 -0
  320. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.cpp +1889 -0
  321. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.f.inc +91 -0
  322. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.f90.inc +603 -0
  323. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.h +2182 -0
  324. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc.hpp +1027 -0
  325. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqcCallbacks.h +19 -0
  326. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqcF.f +653 -0
  327. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqcLib.cpp +1098 -0
  328. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc_interface.F90 +1283 -0
  329. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc_interface_F.cpp +535 -0
  330. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/IPhreeqc_interface_F.h +162 -0
  331. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Makefile.am +210 -0
  332. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Makefile.in +1294 -0
  333. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/README.Fortran +17 -0
  334. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Var.c +84 -0
  335. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Var.h +152 -0
  336. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/Version.h +36 -0
  337. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fimpl.h +282 -0
  338. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap.cpp +646 -0
  339. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap.h +163 -0
  340. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap1.cpp +24 -0
  341. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap2.cpp +24 -0
  342. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap3.cpp +24 -0
  343. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap4.cpp +24 -0
  344. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap5.cpp +24 -0
  345. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap6.cpp +25 -0
  346. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap7.cpp +25 -0
  347. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/fwrap8.cpp +24 -0
  348. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartHandler.cpp +225 -0
  349. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartHandler.h +59 -0
  350. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartObject.cpp +1382 -0
  351. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ChartObject.h +444 -0
  352. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/CurveObject.cpp +42 -0
  353. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/CurveObject.h +79 -0
  354. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Dictionary.cpp +41 -0
  355. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Dictionary.h +28 -0
  356. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ExchComp.cxx +398 -0
  357. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ExchComp.h +117 -0
  358. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Exchange.cxx +466 -0
  359. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Exchange.h +74 -0
  360. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Form1.h +1184 -0
  361. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Form1.resX +36 -0
  362. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasComp.cxx +265 -0
  363. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasComp.h +59 -0
  364. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasPhase.cxx +659 -0
  365. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/GasPhase.h +103 -0
  366. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolution.cxx +40 -0
  367. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolution.h +53 -0
  368. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolutionComp.cxx +202 -0
  369. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ISolutionComp.h +138 -0
  370. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/KineticsComp.cxx +318 -0
  371. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/KineticsComp.h +81 -0
  372. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NA.h +1 -0
  373. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NameDouble.cxx +537 -0
  374. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NameDouble.h +66 -0
  375. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NumKeyword.cxx +190 -0
  376. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/NumKeyword.h +67 -0
  377. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PBasic.cpp +8350 -0
  378. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PBasic.h +572 -0
  379. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PHRQ_io_output.cpp +411 -0
  380. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblage.cxx +375 -0
  381. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblage.h +70 -0
  382. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblageComp.cxx +441 -0
  383. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PPassemblageComp.h +83 -0
  384. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Phreeqc.cpp +2087 -0
  385. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Phreeqc.h +2164 -0
  386. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PhreeqcKeywords/Keywords.cpp +242 -0
  387. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/PhreeqcKeywords/Keywords.h +104 -0
  388. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Pressure.cxx +417 -0
  389. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Pressure.h +43 -0
  390. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Reaction.cxx +284 -0
  391. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Reaction.h +57 -0
  392. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ReadClass.cxx +1150 -0
  393. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SS.cxx +609 -0
  394. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SS.h +128 -0
  395. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SSassemblage.cxx +317 -0
  396. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SSassemblage.h +59 -0
  397. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SScomp.cxx +297 -0
  398. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SScomp.h +66 -0
  399. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SelectedOutput.cpp +115 -0
  400. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SelectedOutput.h +209 -0
  401. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Serializer.cxx +213 -0
  402. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Serializer.h +42 -0
  403. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Solution.cxx +1795 -0
  404. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Solution.h +154 -0
  405. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SolutionIsotope.cxx +333 -0
  406. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SolutionIsotope.h +85 -0
  407. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBin.cxx +1507 -0
  408. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBin.h +141 -0
  409. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBinList.cpp +358 -0
  410. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/StorageBinList.h +81 -0
  411. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Surface.cxx +837 -0
  412. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Surface.h +108 -0
  413. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceCharge.cxx +617 -0
  414. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceCharge.h +137 -0
  415. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceComp.cxx +509 -0
  416. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/SurfaceComp.h +70 -0
  417. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/System.cxx +103 -0
  418. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/System.h +89 -0
  419. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Temperature.cxx +423 -0
  420. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Temperature.h +42 -0
  421. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Use.cpp +78 -0
  422. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/Use.h +159 -0
  423. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/UserPunch.cpp +32 -0
  424. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/UserPunch.h +39 -0
  425. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/ZedGraph.dll +0 -0
  426. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/advection.cpp +140 -0
  427. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/basicsubs.cpp +4333 -0
  428. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cl1.cpp +881 -0
  429. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_base.cxx +117 -0
  430. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_base.h +48 -0
  431. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_exports.h +20 -0
  432. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_io.cpp +914 -0
  433. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/PHRQ_io.h +207 -0
  434. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Parser.cxx +1331 -0
  435. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Parser.h +310 -0
  436. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Utils.cxx +263 -0
  437. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/Utils.h +29 -0
  438. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/common/phrqtype.h +18 -0
  439. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvdense.cpp +566 -0
  440. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvdense.h +267 -0
  441. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvode.cpp +3939 -0
  442. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cvode.h +940 -0
  443. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxKinetics.cxx +617 -0
  444. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxKinetics.h +78 -0
  445. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxMix.cxx +154 -0
  446. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/cxxMix.h +58 -0
  447. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dense.cpp +175 -0
  448. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dense.h +341 -0
  449. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dumper.cpp +277 -0
  450. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/dumper.h +60 -0
  451. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/gases.cpp +748 -0
  452. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/global_structures.h +1672 -0
  453. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/input.cpp +133 -0
  454. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/integrate.cpp +1219 -0
  455. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/inverse.cpp +5135 -0
  456. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/isotopes.cpp +1813 -0
  457. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/kinetics.cpp +3180 -0
  458. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/mainsubs.cpp +2320 -0
  459. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/model.cpp +5843 -0
  460. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector.cpp +272 -0
  461. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector.h +485 -0
  462. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector_serial.cpp +1032 -0
  463. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/nvector_serial.h +369 -0
  464. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/parse.cpp +1044 -0
  465. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/phqalloc.cpp +316 -0
  466. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/phqalloc.h +47 -0
  467. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/pitzer.cpp +2709 -0
  468. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/pitzer_structures.cpp +225 -0
  469. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/prep.cpp +6267 -0
  470. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/print.cpp +3673 -0
  471. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/read.cpp +10245 -0
  472. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/readtr.cpp +1495 -0
  473. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/runner.cpp +158 -0
  474. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/runner.h +33 -0
  475. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sit.cpp +1684 -0
  476. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/smalldense.cpp +324 -0
  477. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/smalldense.h +261 -0
  478. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/spread.cpp +1309 -0
  479. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/step.cpp +1566 -0
  480. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/structures.cpp +3381 -0
  481. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sundialsmath.cpp +133 -0
  482. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sundialsmath.h +162 -0
  483. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/sundialstypes.h +183 -0
  484. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/tally.cpp +1288 -0
  485. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/tidy.cpp +5600 -0
  486. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/transport.cpp +6403 -0
  487. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/phreeqcpp/utilities.cpp +1339 -0
  488. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/src/thread.h +64 -0
  489. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/CMakeLists.txt +133 -0
  490. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/Makefile.am +45 -0
  491. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/Makefile.in +1128 -0
  492. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/ex2.in +26 -0
  493. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/main.f90 +31 -0
  494. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/main77.f +6 -0
  495. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/main_fortran.cxx +8 -0
  496. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/phreeqc.dat.in +1556 -0
  497. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/test_c.c +148 -0
  498. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/test_cxx.cxx +152 -0
  499. pyEQL/phreeqc/ext/iphreeqc-3.8.6-17100/tests/test_f90.F90 +328 -0
  500. pyEQL/phreeqc/iphreeqc_wrapper.cpp +75 -0
  501. pyEQL/phreeqc/solution.py +74 -0
  502. pyEQL/phreeqc/var.py +50 -0
  503. pyEQL/presets/Ringers lactate.yaml +20 -0
  504. pyEQL/presets/__init__.py +17 -0
  505. pyEQL/presets/normal saline.yaml +17 -0
  506. pyEQL/presets/rainwater.yaml +17 -0
  507. pyEQL/presets/seawater.yaml +29 -0
  508. pyEQL/presets/urine.yaml +26 -0
  509. pyEQL/presets/wastewater.yaml +21 -0
  510. pyEQL/py.typed +0 -0
  511. pyEQL/salt_ion_match.py +112 -0
  512. pyEQL/solute.py +163 -0
  513. pyEQL/solution.py +2714 -0
  514. pyEQL/utils.py +237 -0
  515. pyeql-1.4.0rc9.dist-info/METADATA +130 -0
  516. pyeql-1.4.0rc9.dist-info/RECORD +519 -0
  517. pyeql-1.4.0rc9.dist-info/WHEEL +5 -0
  518. pyeql-1.4.0rc9.dist-info/licenses/AUTHORS.md +21 -0
  519. 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