pychnosz 1.1.1__cp311-cp311-macosx_10_13_x86_64.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 (131) hide show
  1. pychnosz/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. pychnosz/.dylibs/libgfortran.5.dylib +0 -0
  3. pychnosz/.dylibs/libquadmath.0.dylib +0 -0
  4. pychnosz/__init__.py +129 -0
  5. pychnosz/biomolecules/__init__.py +29 -0
  6. pychnosz/biomolecules/ionize_aa.py +197 -0
  7. pychnosz/biomolecules/proteins.py +595 -0
  8. pychnosz/core/__init__.py +46 -0
  9. pychnosz/core/affinity.py +1256 -0
  10. pychnosz/core/animation.py +593 -0
  11. pychnosz/core/balance.py +334 -0
  12. pychnosz/core/basis.py +716 -0
  13. pychnosz/core/diagram.py +3336 -0
  14. pychnosz/core/equilibrate.py +813 -0
  15. pychnosz/core/equilibrium.py +554 -0
  16. pychnosz/core/info.py +821 -0
  17. pychnosz/core/retrieve.py +364 -0
  18. pychnosz/core/speciation.py +580 -0
  19. pychnosz/core/species.py +599 -0
  20. pychnosz/core/subcrt.py +1700 -0
  21. pychnosz/core/thermo.py +593 -0
  22. pychnosz/core/unicurve.py +1226 -0
  23. pychnosz/data/__init__.py +11 -0
  24. pychnosz/data/add_obigt.py +327 -0
  25. pychnosz/data/extdata/Berman/BDat17_2017.csv +2 -0
  26. pychnosz/data/extdata/Berman/Ber88_1988.csv +68 -0
  27. pychnosz/data/extdata/Berman/Ber90_1990.csv +5 -0
  28. pychnosz/data/extdata/Berman/DS10_2010.csv +6 -0
  29. pychnosz/data/extdata/Berman/FDM+14_2014.csv +2 -0
  30. pychnosz/data/extdata/Berman/Got04_2004.csv +5 -0
  31. pychnosz/data/extdata/Berman/JUN92_1992.csv +3 -0
  32. pychnosz/data/extdata/Berman/SHD91_1991.csv +12 -0
  33. pychnosz/data/extdata/Berman/VGT92_1992.csv +2 -0
  34. pychnosz/data/extdata/Berman/VPT01_2001.csv +3 -0
  35. pychnosz/data/extdata/Berman/VPV05_2005.csv +2 -0
  36. pychnosz/data/extdata/Berman/ZS92_1992.csv +11 -0
  37. pychnosz/data/extdata/Berman/sympy.R +99 -0
  38. pychnosz/data/extdata/Berman/testing/BA96.bib +12 -0
  39. pychnosz/data/extdata/Berman/testing/BA96_Berman.csv +21 -0
  40. pychnosz/data/extdata/Berman/testing/BA96_OBIGT.csv +21 -0
  41. pychnosz/data/extdata/Berman/testing/BA96_refs.csv +6 -0
  42. pychnosz/data/extdata/OBIGT/AD.csv +25 -0
  43. pychnosz/data/extdata/OBIGT/Berman_cr.csv +93 -0
  44. pychnosz/data/extdata/OBIGT/DEW.csv +211 -0
  45. pychnosz/data/extdata/OBIGT/H2O_aq.csv +4 -0
  46. pychnosz/data/extdata/OBIGT/SLOP98.csv +411 -0
  47. pychnosz/data/extdata/OBIGT/SUPCRT92.csv +178 -0
  48. pychnosz/data/extdata/OBIGT/inorganic_aq.csv +729 -0
  49. pychnosz/data/extdata/OBIGT/inorganic_cr.csv +273 -0
  50. pychnosz/data/extdata/OBIGT/inorganic_gas.csv +20 -0
  51. pychnosz/data/extdata/OBIGT/organic_aq.csv +1104 -0
  52. pychnosz/data/extdata/OBIGT/organic_cr.csv +481 -0
  53. pychnosz/data/extdata/OBIGT/organic_gas.csv +268 -0
  54. pychnosz/data/extdata/OBIGT/organic_liq.csv +533 -0
  55. pychnosz/data/extdata/OBIGT/testing/GEMSFIT.csv +43 -0
  56. pychnosz/data/extdata/OBIGT/testing/IGEM.csv +17 -0
  57. pychnosz/data/extdata/OBIGT/testing/Sandia.csv +8 -0
  58. pychnosz/data/extdata/OBIGT/testing/SiO2.csv +4 -0
  59. pychnosz/data/extdata/misc/AD03_Fig1a.csv +69 -0
  60. pychnosz/data/extdata/misc/AD03_Fig1b.csv +43 -0
  61. pychnosz/data/extdata/misc/AD03_Fig1c.csv +89 -0
  62. pychnosz/data/extdata/misc/AD03_Fig1d.csv +30 -0
  63. pychnosz/data/extdata/misc/BZA10.csv +5 -0
  64. pychnosz/data/extdata/misc/HW97_Cp.csv +90 -0
  65. pychnosz/data/extdata/misc/HWM96_V.csv +229 -0
  66. pychnosz/data/extdata/misc/LA19_test.csv +7 -0
  67. pychnosz/data/extdata/misc/Mer75_Table4.csv +42 -0
  68. pychnosz/data/extdata/misc/OBIGT_check.csv +423 -0
  69. pychnosz/data/extdata/misc/PM90.csv +7 -0
  70. pychnosz/data/extdata/misc/RH95.csv +23 -0
  71. pychnosz/data/extdata/misc/RH98_Table15.csv +17 -0
  72. pychnosz/data/extdata/misc/SC10_Rainbow.csv +19 -0
  73. pychnosz/data/extdata/misc/SK95.csv +55 -0
  74. pychnosz/data/extdata/misc/SOJSH.csv +61 -0
  75. pychnosz/data/extdata/misc/SS98_Fig5a.csv +81 -0
  76. pychnosz/data/extdata/misc/SS98_Fig5b.csv +84 -0
  77. pychnosz/data/extdata/misc/TKSS14_Fig2.csv +25 -0
  78. pychnosz/data/extdata/misc/bluered.txt +1000 -0
  79. pychnosz/data/extdata/protein/Cas/Cas_aa.csv +177 -0
  80. pychnosz/data/extdata/protein/Cas/Cas_uniprot.csv +186 -0
  81. pychnosz/data/extdata/protein/Cas/download.R +34 -0
  82. pychnosz/data/extdata/protein/Cas/mkaa.R +34 -0
  83. pychnosz/data/extdata/protein/POLG.csv +12 -0
  84. pychnosz/data/extdata/protein/TBD+05.csv +393 -0
  85. pychnosz/data/extdata/protein/TBD+05_aa.csv +393 -0
  86. pychnosz/data/extdata/protein/rubisco.csv +28 -0
  87. pychnosz/data/extdata/protein/rubisco.fasta +239 -0
  88. pychnosz/data/extdata/protein/rubisco_aa.csv +28 -0
  89. pychnosz/data/extdata/src/H2O92D.f.orig +3457 -0
  90. pychnosz/data/extdata/src/README.txt +5 -0
  91. pychnosz/data/extdata/taxonomy/names.dmp +215 -0
  92. pychnosz/data/extdata/taxonomy/nodes.dmp +63 -0
  93. pychnosz/data/extdata/thermo/Bdot_acirc.csv +60 -0
  94. pychnosz/data/extdata/thermo/buffer.csv +40 -0
  95. pychnosz/data/extdata/thermo/element.csv +135 -0
  96. pychnosz/data/extdata/thermo/groups.csv +6 -0
  97. pychnosz/data/extdata/thermo/opt.csv +2 -0
  98. pychnosz/data/extdata/thermo/protein.csv +506 -0
  99. pychnosz/data/extdata/thermo/refs.csv +343 -0
  100. pychnosz/data/extdata/thermo/stoich.csv.xz +0 -0
  101. pychnosz/data/loader.py +431 -0
  102. pychnosz/data/mod_obigt.py +322 -0
  103. pychnosz/data/obigt.py +471 -0
  104. pychnosz/data/worm.py +228 -0
  105. pychnosz/fortran/__init__.py +16 -0
  106. pychnosz/fortran/h2o92.dylib +0 -0
  107. pychnosz/fortran/h2o92_interface.py +527 -0
  108. pychnosz/geochemistry/__init__.py +21 -0
  109. pychnosz/geochemistry/minerals.py +514 -0
  110. pychnosz/geochemistry/redox.py +500 -0
  111. pychnosz/models/__init__.py +47 -0
  112. pychnosz/models/archer_wang.py +165 -0
  113. pychnosz/models/berman.py +309 -0
  114. pychnosz/models/cgl.py +381 -0
  115. pychnosz/models/dew.py +997 -0
  116. pychnosz/models/hkf.py +523 -0
  117. pychnosz/models/hkf_helpers.py +222 -0
  118. pychnosz/models/iapws95.py +1113 -0
  119. pychnosz/models/supcrt92_fortran.py +238 -0
  120. pychnosz/models/water.py +480 -0
  121. pychnosz/utils/__init__.py +27 -0
  122. pychnosz/utils/expression.py +1074 -0
  123. pychnosz/utils/formula.py +830 -0
  124. pychnosz/utils/formula_ox.py +227 -0
  125. pychnosz/utils/reset.py +33 -0
  126. pychnosz/utils/units.py +259 -0
  127. pychnosz-1.1.1.dist-info/METADATA +197 -0
  128. pychnosz-1.1.1.dist-info/RECORD +131 -0
  129. pychnosz-1.1.1.dist-info/WHEEL +5 -0
  130. pychnosz-1.1.1.dist-info/licenses/LICENSE.txt +19 -0
  131. pychnosz-1.1.1.dist-info/top_level.txt +1 -0
pychnosz/models/hkf.py ADDED
@@ -0,0 +1,523 @@
1
+ """
2
+ HKF (Helgeson-Kirkham-Flowers) equation of state implementation.
3
+
4
+ This module implements the revised HKF equations for calculating thermodynamic
5
+ properties of aqueous species, based on the tested functions from HKF_cgl.py.
6
+
7
+ References:
8
+ - Shock, E.L. et al. (1992). Calculation of the thermodynamic properties of
9
+ aqueous species at high pressures and temperatures. J. Chem. Soc. Faraday Trans.
10
+ - Johnson, J.W. et al. (1992). SUPCRT92: A software package for calculating the
11
+ standard molal thermodynamic properties. Computers & Geosciences.
12
+ - R CHNOSZ hkf.R implementation
13
+ """
14
+
15
+ import pandas as pd
16
+ import numpy as np
17
+ import math
18
+ import copy
19
+ import warnings
20
+ from .water import water
21
+
22
+ def convert_cm3bar(value):
23
+ return value*4.184 * 10
24
+
25
+ def gfun(rhohat, Tc, P, alpha, daldT, beta):
26
+ ## g and f functions for describing effective electrostatic radii of ions
27
+ ## split from hkf() 20120123 jmd
28
+ ## based on equations in
29
+ ## Shock EL, Oelkers EH, Johnson JW, Sverjensky DA, Helgeson HC, 1992
30
+ ## Calculation of the Thermodynamic Properties of Aqueous Species at High Pressures
31
+ ## and Temperatures: Effective Electrostatic Radii, Dissociation Constants and
32
+ ## Standard Partial Molal Properties to 1000 degrees C and 5 kbar
33
+ ## J. Chem. Soc. Faraday Trans., 88(6), 803-826 doi:10.1039/FT9928800803
34
+ # rhohat - density of water in g/cm3
35
+ # Tc - temperature in degrees Celsius
36
+ # P - pressure in bars
37
+
38
+ # Vectorized version - handle both scalars and arrays
39
+ rhohat = np.atleast_1d(rhohat)
40
+ Tc = np.atleast_1d(Tc)
41
+ P = np.atleast_1d(P)
42
+ alpha = np.atleast_1d(alpha)
43
+ daldT = np.atleast_1d(daldT)
44
+ beta = np.atleast_1d(beta)
45
+
46
+ # Broadcast to same shape
47
+ shape = np.broadcast_shapes(rhohat.shape, Tc.shape, P.shape, alpha.shape, daldT.shape, beta.shape)
48
+ rhohat = np.broadcast_to(rhohat, shape)
49
+ Tc = np.broadcast_to(Tc, shape)
50
+ P = np.broadcast_to(P, shape)
51
+ alpha = np.broadcast_to(alpha, shape)
52
+ daldT = np.broadcast_to(daldT, shape)
53
+ beta = np.broadcast_to(beta, shape)
54
+
55
+ # Initialize output arrays
56
+ g = np.zeros(shape)
57
+ dgdT = np.zeros(shape)
58
+ d2gdT2 = np.zeros(shape)
59
+ dgdP = np.zeros(shape)
60
+
61
+ # only rhohat less than 1 will give results other than zero
62
+ mask = rhohat < 1
63
+ if not np.any(mask):
64
+ return {"g": g, "dgdT": dgdT, "d2gdT2": d2gdT2, "dgdP": dgdP}
65
+
66
+ # eta in Eq. 1
67
+ eta = 1.66027E5
68
+ # Table 3
69
+ ag1 = -2.037662
70
+ ag2 = 5.747000E-3
71
+ ag3 = -6.557892E-6
72
+ bg1 = 6.107361
73
+ bg2 = -1.074377E-2
74
+ bg3 = 1.268348E-5
75
+
76
+ # Work only with masked values
77
+ Tc_m = Tc[mask]
78
+ P_m = P[mask]
79
+ rhohat_m = rhohat[mask]
80
+ alpha_m = alpha[mask]
81
+ daldT_m = daldT[mask]
82
+ beta_m = beta[mask]
83
+
84
+ # Eq. 25
85
+ ag = ag1 + ag2 * Tc_m + ag3 * Tc_m ** 2
86
+ # Eq. 26
87
+ bg = bg1 + bg2 * Tc_m + bg3 * Tc_m ** 2
88
+ # Eq. 24
89
+ g_m = ag * (1 - rhohat_m) ** bg
90
+
91
+ # Table 4
92
+ af1 = 0.3666666E2
93
+ af2 = -0.1504956E-9
94
+ af3 = 0.5017997E-13
95
+
96
+ # Eq. 33
97
+ f = ( ((Tc_m - 155) / 300) ** 4.8 + af1 * ((Tc_m - 155) / 300) ** 16 ) * \
98
+ ( af2 * (1000 - P_m) ** 3 + af3 * (1000 - P_m) ** 4 )
99
+
100
+ # limits of the f function (region II of Fig. 6)
101
+ ifg = (Tc_m > 155) & (P_m < 1000) & (Tc_m < 355)
102
+
103
+ # Eq. 32 - apply f correction where ifg is True
104
+ # Check for complex values
105
+ f_is_real = ~np.iscomplex(f)
106
+ apply_f = ifg & f_is_real
107
+ g_m = np.where(apply_f, g_m - f.real, g_m)
108
+
109
+ # at P > 6000 bar (in DEW calculations), g is zero 20170926
110
+ g_m = np.where(P_m > 6000, 0, g_m)
111
+
112
+ ## now we have g at P, T
113
+ # put the results in their right place (where rhohat < 1)
114
+ g[mask] = g_m
115
+
116
+ ## the rest is to get its partial derivatives with pressure and temperature
117
+ ## after Johnson et al., 1992
118
+ # alpha - coefficient of isobaric expansivity (K^-1)
119
+ # daldT - temperature derivative of coefficient of isobaric expansivity (K^-2)
120
+ # beta - coefficient of isothermal compressibility (bar^-1)
121
+
122
+ # Eqn. 76
123
+ d2fdT2 = (0.0608/300*((Tc_m-155)/300)**2.8 + af1/375*((Tc_m-155)/300)**14) * (af2*(1000-P_m)**3 + af3*(1000-P_m)**4)
124
+ # Eqn. 75
125
+ dfdT = (0.016*((Tc_m-155)/300)**3.8 + 16*af1/300*((Tc_m-155)/300)**15) * \
126
+ (af2*(1000-P_m)**3 + af3*(1000-P_m)**4)
127
+ # Eqn. 74
128
+ dfdP = -(((Tc_m-155)/300)**4.8 + af1*((Tc_m-155)/300)**16) * \
129
+ (3*af2*(1000-P_m)**2 + 4*af3*(1000-P_m)**3)
130
+ d2bdT2 = 2 * bg3 # Eqn. 73
131
+ d2adT2 = 2 * ag3 # Eqn. 72
132
+ dbdT = bg2 + 2*bg3*Tc_m # Eqn. 71
133
+ dadT = ag2 + 2*ag3*Tc_m # Eqn. 70
134
+
135
+ # Convert complex to NaN
136
+ d2fdT2 = np.where(np.iscomplex(d2fdT2), np.nan, np.real(d2fdT2))
137
+ dfdT = np.where(np.iscomplex(dfdT), np.nan, np.real(dfdT))
138
+ dfdP = np.where(np.iscomplex(dfdP), np.nan, np.real(dfdP))
139
+
140
+ # Initialize derivative arrays for masked region
141
+ dgdT_m = np.zeros_like(g_m)
142
+ d2gdT2_m = np.zeros_like(g_m)
143
+ dgdP_m = np.zeros_like(g_m)
144
+
145
+ # Calculate derivatives where alpha and daldT are not NaN
146
+ alpha_valid = ~np.isnan(alpha_m) & ~np.isnan(daldT_m)
147
+ if np.any(alpha_valid):
148
+ # Work with valid subset
149
+ av_idx = alpha_valid
150
+ bg_av = bg[av_idx]
151
+ rhohat_av = rhohat_m[av_idx]
152
+ alpha_av = alpha_m[av_idx]
153
+ daldT_av = daldT_m[av_idx]
154
+ g_av = g_m[av_idx]
155
+ ag_av = ag[av_idx]
156
+ Tc_av = Tc_m[av_idx]
157
+ dbdT_av = dbdT[av_idx]
158
+ dadT_av = dadT[av_idx]
159
+
160
+ # Handle log of (1-rhohat) safely
161
+ with np.errstate(divide='ignore', invalid='ignore'):
162
+ log_term = np.log(1 - rhohat_av)
163
+ log_term = np.where(np.isfinite(log_term), log_term, 0)
164
+
165
+ # Eqn. 69
166
+ dgadT = bg_av*rhohat_av*alpha_av*(1-rhohat_av)**(bg_av-1) + log_term*g_av/ag_av*dbdT_av
167
+ D = rhohat_av
168
+
169
+ # transcribed from SUPCRT92/reac92.f
170
+ dDdT = -D * alpha_av
171
+ dDdTT = -D * (daldT_av - alpha_av**2)
172
+ Db = (1-D)**bg_av
173
+ dDbdT = -bg_av*(1-D)**(bg_av-1)*dDdT + log_term*Db*dbdT_av
174
+ dDbdTT = -(bg_av*(1-D)**(bg_av-1)*dDdTT + (1-D)**(bg_av-1)*dDdT*dbdT_av + \
175
+ bg_av*dDdT*(-(bg_av-1)*(1-D)**(bg_av-2)*dDdT + log_term*(1-D)**(bg_av-1)*dbdT_av)) + \
176
+ log_term*(1-D)**bg_av*d2bdT2 - (1-D)**bg_av*dbdT_av*dDdT/(1-D) + log_term*dbdT_av*dDbdT
177
+ d2gdT2_calc = ag_av*dDbdTT + 2*dDbdT*dadT_av + Db*d2adT2
178
+
179
+ # Apply f correction where ifg is True
180
+ ifg_av = ifg[av_idx]
181
+ d2fdT2_av = d2fdT2[av_idx]
182
+ dfdT_av = dfdT[av_idx]
183
+ d2gdT2_calc = np.where(ifg_av, d2gdT2_calc - d2fdT2_av, d2gdT2_calc)
184
+
185
+ dgdT_calc = g_av/ag_av*dadT_av + ag_av*dgadT # Eqn. 67
186
+ dgdT_calc = np.where(ifg_av, dgdT_calc - dfdT_av, dgdT_calc)
187
+
188
+ dgdT_m[av_idx] = dgdT_calc
189
+ d2gdT2_m[av_idx] = d2gdT2_calc
190
+
191
+ # Calculate dgdP where beta is not NaN
192
+ beta_valid = ~np.isnan(beta_m)
193
+ if np.any(beta_valid):
194
+ bv_idx = beta_valid
195
+ bg_bv = bg[bv_idx]
196
+ rhohat_bv = rhohat_m[bv_idx]
197
+ beta_bv = beta_m[bv_idx]
198
+ g_bv = g_m[bv_idx]
199
+
200
+ dgdP_calc = -bg_bv*rhohat_bv*beta_bv*g_bv*(1-rhohat_bv)**-1 # Eqn. 66
201
+ ifg_bv = ifg[bv_idx]
202
+ dfdP_bv = dfdP[bv_idx]
203
+ dgdP_calc = np.where(ifg_bv, dgdP_calc - dfdP_bv, dgdP_calc)
204
+ dgdP_m[bv_idx] = dgdP_calc
205
+
206
+ # Put results back into full arrays
207
+ dgdT[mask] = dgdT_m
208
+ d2gdT2[mask] = d2gdT2_m
209
+ dgdP[mask] = dgdP_m
210
+
211
+ return {"g": g, "dgdT": dgdT, "d2gdT2": d2gdT2, "dgdP": dgdP}
212
+
213
+ def hkf(property=None, parameters=None, T=298.15, P=1,
214
+ contrib = ["n", "s", "o"], H2O_props=["rho"], water_model="SUPCRT92"):
215
+ # calculate G, H, S, Cp, V, kT, and/or E using
216
+ # the revised HKF equations of state
217
+ # H2O_props - H2O properties needed for subcrt() output
218
+ # constants
219
+ Tr = 298.15 # K
220
+ Pr = 1 # bar
221
+ Theta = 228 # K
222
+ Psi = 2600 # bar
223
+
224
+ # Convert T and P to arrays for vectorized operations
225
+ T = np.atleast_1d(T)
226
+ P = np.atleast_1d(P)
227
+
228
+ # DEBUG
229
+ if False:
230
+ print(f"\nDEBUG HKF input:")
231
+ print(f" T (K): {T}")
232
+ print(f" P (bar): {P}")
233
+
234
+ # make T and P equal length
235
+ if P.size < T.size:
236
+ P = np.full_like(T, P[0] if P.size == 1 else P)
237
+ if T.size < P.size:
238
+ T = np.full_like(P, T[0] if T.size == 1 else T)
239
+
240
+ n_conditions = T.size
241
+
242
+ # GB conversion note: handle error messages later
243
+ # # nonsolvation, solvation, and origination contribution
244
+ # notcontrib <- ! contrib %in% c("n", "s", "o")
245
+ # if(TRUE %in% notcontrib) stop(paste("contrib must be in c('n', 's', 'o); got", c2s(contrib[notcontrib])))
246
+
247
+ # get water properties
248
+ # rho - for subcrt() output and g function
249
+ # Born functions and epsilon - for HKF calculations
250
+ H2O_props += ["QBorn", "XBorn", "YBorn", "epsilon"]
251
+
252
+ if water_model == "SUPCRT92":
253
+ # using H2O92D.f from SUPCRT92: alpha, daldT, beta - for partial derivatives of omega (g function)
254
+ H2O_props += ["alpha", "daldT", "beta"]
255
+
256
+ elif water_model == "IAPWS95":
257
+ # using IAPWS-95: NBorn, UBorn - for compressibility, expansibility
258
+ H2O_props += ["alpha", "daldT", "beta", "NBorn", "UBorn"]
259
+
260
+ elif water_model == "DEW":
261
+ # using DEW model: get beta to calculate dgdP
262
+ H2O_props += ["alpha", "daldT", "beta"]
263
+
264
+ # DEBUG: Print T and P being passed to water
265
+ if False:
266
+ print(f"DEBUG HKF calling water():")
267
+ print(f" T type: {type(T)}, T: {T}")
268
+ print(f" P type: {type(P)}, P: {P}")
269
+ print(f" H2O_props: {H2O_props}")
270
+
271
+ H2O_PrTr = water(H2O_props, T=Tr, P=Pr)
272
+ H2O_PT = water(H2O_props, T=T, P=P)
273
+
274
+ # DEBUG: Print what water returned
275
+ if False:
276
+ print(f"DEBUG HKF water() returned:")
277
+ print(f" H2O_PT type: {type(H2O_PT)}")
278
+ if isinstance(H2O_PT, dict):
279
+ print(f" H2O_PT keys: {H2O_PT.keys()}")
280
+ print(f" epsilon: {H2O_PT.get('epsilon', 'NOT FOUND')}")
281
+
282
+ # Handle dict output from water function
283
+ def get_water_prop(water_dict, prop):
284
+ """Helper function to get water property from dict or DataFrame"""
285
+ if isinstance(water_dict, dict):
286
+ return water_dict[prop]
287
+ else:
288
+ return water_dict.loc["1", prop]
289
+
290
+ # Get epsilon values and handle potential zeros
291
+ epsilon_PT = get_water_prop(H2O_PT, "epsilon")
292
+ epsilon_PrTr = get_water_prop(H2O_PrTr, "epsilon")
293
+
294
+ # Check for zero or very small epsilon values and warn
295
+ if np.any(epsilon_PT == 0) or np.any(np.abs(epsilon_PT) < 1e-10):
296
+ warnings.warn(f"HKF: epsilon at P,T is zero or very small: {epsilon_PT}. H2O_PT keys: {H2O_PT.keys() if isinstance(H2O_PT, dict) else 'not dict'}")
297
+
298
+ with np.errstate(divide='ignore', invalid='ignore'):
299
+ ZBorn = -1 / epsilon_PT
300
+ ZBorn_PrTr = -1 / epsilon_PrTr
301
+
302
+ # a class to store the result
303
+ out_dict = {} # dictionary to store output
304
+
305
+ for k in parameters.index:
306
+
307
+ if parameters["state"][k] != "aq":
308
+ out_dict[k] = {p:float('NaN') for p in property}
309
+ else:
310
+ sp = parameters["name"][k]
311
+
312
+ # loop over each species
313
+ PAR = copy.copy(parameters.loc[k, :])
314
+
315
+ PAR["a1.a"] = copy.copy(PAR["a1.a"]*10**-1)
316
+ PAR["a2.b"] = copy.copy(PAR["a2.b"]*10**2)
317
+ PAR["a4.d"] = copy.copy(PAR["a4.d"]*10**4)
318
+ PAR["c2.f"] = copy.copy(PAR["c2.f"]*10**4)
319
+ PAR["omega.lambda"] = copy.copy(PAR["omega.lambda"]*10**5)
320
+
321
+ # substitute Cp and V for missing EoS parameters
322
+ # here we assume that the parameters are in the same position as in thermo()$OBIGT
323
+ # we don't need this if we're just looking at solvation properties (Cp_s_var, V_s_var)
324
+
325
+ # GB conversion note: this block checks various things about EOS parameters.
326
+ # for now, just set hasEOS to True
327
+ hasEOS = True # delete this once the following block is converted to python
328
+ # if "n" in contrib:
329
+ # # put the heat capacity in for c1 if both c1 and c2 are missing
330
+ # if all(is.na(PAR[, 18:19])):
331
+ # PAR[, 18] = PAR["Cp"]
332
+ # # put the volume in for a1 if a1, a2, a3 and a4 are missing
333
+ # if all(is.na(PAR[, 14:17])):
334
+ # PAR[, 14] = convert(PAR["V"], "calories")
335
+ # # test for availability of the EoS parameters
336
+ # hasEOS = any(!is.na(PAR[, 14:21]))
337
+ # # if at least one of the EoS parameters is available, zero out any NA's in the rest
338
+ # if hasEOS:
339
+ # PAR[, 14:21][, is.na(PAR[, 14:21])] = 0
340
+
341
+ # compute values of omega(P,T) from those of omega(Pr,Tr)
342
+ # using g function etc. (Shock et al., 1992 and others)
343
+ omega = PAR["omega.lambda"] # omega_PrTr
344
+ # its derivatives are zero unless the g function kicks in
345
+ dwdP = np.zeros(n_conditions)
346
+ dwdT = np.zeros(n_conditions)
347
+ d2wdT2 = np.zeros(n_conditions)
348
+ Z = PAR["z.T"]
349
+
350
+ omega_PT = np.full(n_conditions, PAR["omega.lambda"])
351
+ if Z != 0 and Z != "NA" and PAR["name"] != "H+":
352
+ # compute derivatives of omega: g and f functions (Shock et al., 1992; Johnson et al., 1992)
353
+ rhohat = get_water_prop(H2O_PT, "rho")/1000 # just converting kg/m3 to g/cm3
354
+
355
+ # temporarily filter out Python's warnings about dividing by zero, which is possible
356
+ # with the equations in the gfunction
357
+ # Possible complex output is acounted for in gfun().
358
+ with warnings.catch_warnings():
359
+ warnings.simplefilter('ignore')
360
+ g = gfun(rhohat, T-273.15, P, get_water_prop(H2O_PT, "alpha"), get_water_prop(H2O_PT, "daldT"), get_water_prop(H2O_PT, "beta"))
361
+
362
+ # after SUPCRT92/reac92.f
363
+ eta = 1.66027E5
364
+ reref = (Z**2) / (omega/eta + Z/(3.082 + 0))
365
+ re = reref + abs(Z) * g["g"]
366
+ omega_PT = eta * (Z**2/re - Z/(3.082 + g["g"]))
367
+ Z3 = abs(Z**3)/re**2 - Z/(3.082 + g["g"])**2
368
+ Z4 = abs(Z**4)/re**3 - Z/(3.082 + g["g"])**3
369
+ dwdP = (-eta * Z3 * g["dgdP"])
370
+ dwdT = (-eta * Z3 * g["dgdT"])
371
+ d2wdT2 = (2 * eta * Z4 * g["dgdT"]**2 - eta * Z3 * g["d2gdT2"])
372
+
373
+ # loop over each property
374
+ w = float('NaN')
375
+ for i,PROP in enumerate(property) :
376
+
377
+ # over nonsolvation, solvation, or origination contributions - vectorized
378
+ hkf_p = np.zeros(n_conditions)
379
+
380
+ for icontrib in contrib :
381
+ # various contributions to the properties
382
+ if icontrib == "n":
383
+ # nonsolvation ghs equations
384
+ if PROP == "H":
385
+ p_c = PAR["c1.e"]*(T-Tr) - PAR["c2.f"]*(1/(T-Theta)-1/(Tr-Theta))
386
+ p_a = PAR["a1.a"]*(P-Pr) + PAR["a2.b"]*np.log((Psi+P)/(Psi+Pr)) + \
387
+ ((2*T-Theta)/(T-Theta)**2)*(PAR["a3.c"]*(P-Pr)+PAR["a4.d"]*np.log((Psi+P)/(Psi+Pr)))
388
+ p = p_c + p_a
389
+ elif PROP == "S":
390
+ p_c = PAR["c1.e"]*np.log(T/Tr) - \
391
+ (PAR["c2.f"]/Theta)*( 1/(T-Theta)-1/(Tr-Theta) + \
392
+ np.log( (Tr*(T-Theta))/(T*(Tr-Theta)) )/Theta )
393
+ p_a = (T-Theta)**(-2)*(PAR["a3.c"]*(P-Pr)+PAR["a4.d"]*np.log((Psi+P)/(Psi+Pr)))
394
+ p = p_c + p_a
395
+ elif PROP == "G":
396
+ p_c = -PAR["c1.e"]*(T*np.log(T/Tr)-T+Tr) - \
397
+ PAR["c2.f"]*( (1/(T-Theta)-1/(Tr-Theta))*((Theta-T)/Theta) - \
398
+ (T/Theta**2)*np.log((Tr*(T-Theta))/(T*(Tr-Theta))) )
399
+ p_a = PAR["a1.a"]*(P-Pr) + PAR["a2.b"]*np.log((Psi+P)/(Psi+Pr)) + \
400
+ (PAR["a3.c"]*(P-Pr) + PAR["a4.d"]*np.log((Psi+P)/(Psi+Pr)))/(T-Theta)
401
+ p = p_c + p_a
402
+ # at Tr,Pr, if the origination contribution is not NA, ensure the solvation contribution is 0, not NA
403
+ if not np.isnan(PAR["G"]):
404
+ p = np.where((T==Tr) & (P==Pr), 0, p)
405
+ # nonsolvation cp v kt e equations
406
+ elif PROP == "Cp":
407
+ p = PAR["c1.e"] + PAR["c2.f"] * ( T - Theta ) ** (-2)
408
+ elif PROP == "V":
409
+ p = convert_cm3bar(PAR["a1.a"]) + \
410
+ convert_cm3bar(PAR["a2.b"]) / (Psi + P) + \
411
+ (convert_cm3bar(PAR["a3.c"]) + convert_cm3bar(PAR["a4.d"]) / (Psi + P)) / (T - Theta)
412
+ # elif PROP == "kT":
413
+ # p = (convert(PAR["a2.b"], "cm3bar") + \
414
+ # convert(PAR["a4.d"], "cm3bar") / (T - Theta)) * (Psi + P) ** (-2)
415
+ # elif PROP == "E":
416
+ # p = convert( - (PAR["a3.c"] + PAR["a4.d"] / convert((Psi + P), "calories")) * \
417
+ # (T - Theta) ** (-2), "cm3bar")
418
+ else:
419
+ print("BAD")
420
+
421
+ if icontrib == "s":
422
+ # solvation ghs equations
423
+ if PROP == "G":
424
+ p = -omega_PT*(ZBorn+1) + omega*(ZBorn_PrTr+1) + omega*get_water_prop(H2O_PrTr, "YBorn")*(T-Tr)
425
+ # at Tr,Pr, if the origination contribution is not NA, ensure the solvation contribution is 0, not NA
426
+ if(np.isnan(PAR["G"])):
427
+ p = np.where((T==Tr) & (P==Pr), 0, p)
428
+ if PROP == "H":
429
+ p = -omega_PT*(ZBorn+1) + omega_PT*T*get_water_prop(H2O_PT, "YBorn") + T*(ZBorn+1)*dwdT + \
430
+ omega*(ZBorn_PrTr+1) - omega*Tr*get_water_prop(H2O_PrTr, "YBorn")
431
+ if PROP == "S":
432
+ p = omega_PT*get_water_prop(H2O_PT, "YBorn") + (ZBorn+1)*dwdT - omega*get_water_prop(H2O_PrTr, "YBorn")
433
+ # solvation cp v kt e equations
434
+ if PROP == "Cp":
435
+ p = omega_PT*T*get_water_prop(H2O_PT, "XBorn") + 2*T*get_water_prop(H2O_PT, "YBorn")*dwdT + T*(ZBorn+1)*d2wdT2
436
+ if PROP == "V":
437
+ term1 = -convert_cm3bar(omega_PT) * get_water_prop(H2O_PT, "QBorn")
438
+ term2 = convert_cm3bar(dwdP) * (-ZBorn - 1)
439
+ p = term1 + term2
440
+
441
+ # DEBUG
442
+ if False:
443
+ print(f"\nDEBUG solvation V terms:")
444
+ print(f" omega_PT: {omega_PT}")
445
+ print(f" QBorn: {get_water_prop(H2O_PT, 'QBorn')}")
446
+ print(f" dwdP: {dwdP}")
447
+ print(f" ZBorn: {ZBorn}")
448
+ print(f" term1 (-ω*QBorn): {term1}")
449
+ print(f" term2 (dwdP*(-Z-1)): {term2}")
450
+ print(f" total p: {p}")
451
+ # TODO: the partial derivatives of omega are not included here here for kt and e
452
+ # (to do it, see p. 820 of SOJ+92 ... but kt requires d2wdP2 which we don"t have yet)
453
+ if PROP == "kT":
454
+ p = convert_cm3bar(omega) * get_water_prop(H2O_PT, "NBorn")
455
+ if PROP == "E":
456
+ p = -convert_cm3bar(omega) * get_water_prop(H2O_PT, "UBorn")
457
+
458
+ if icontrib == "o":
459
+ # origination ghs equations
460
+ if PROP == "G":
461
+ p = PAR["G"] - PAR["S"] * (T-Tr)
462
+ # don"t inherit NA from PAR$S at Tr
463
+ p = np.where(T == Tr, PAR["G"], p)
464
+ elif PROP == "H":
465
+ p = np.full(n_conditions, PAR["H"])
466
+ elif PROP == "S":
467
+ p = np.full(n_conditions, PAR["S"])
468
+ # origination eos equations (Cp, V, kT, E): senseless
469
+ else:
470
+ p = np.zeros(n_conditions)
471
+
472
+ # accumulate the contribution
473
+ hkf_p = hkf_p + p
474
+
475
+ # DEBUG
476
+ if False and PROP == "V":
477
+ print(f"\nDEBUG HKF V calculation (species {k}, contrib={icontrib}):")
478
+ print(f" T: {T}")
479
+ print(f" P: {P}")
480
+ print(f" contribution p: {p}")
481
+ print(f" accumulated hkf_p: {hkf_p}")
482
+
483
+ # species have to be numbered (k) instead of named because of name repeats in db (e.g., cr polymorphs)
484
+ if i > 0:
485
+ out_dict[k][PROP] = hkf_p
486
+ else:
487
+ out_dict[k] = {PROP:hkf_p}
488
+
489
+ # DEBUG
490
+ if False and PROP == "V":
491
+ print(f"\nDEBUG HKF final V for species {k}: {hkf_p}")
492
+
493
+ return(out_dict, H2O_PT)
494
+
495
+
496
+ def calculate_born_functions(T: np.ndarray, P) -> dict:
497
+ """Calculate Born functions needed for HKF solvation calculations."""
498
+
499
+ # Get water properties needed for Born functions
500
+ water_props = water(["epsilon", "rho"], T=T, P=P)
501
+
502
+ # Basic Born functions (simplified - full implementation would include all derivatives)
503
+ epsilon = water_props['epsilon']
504
+ rho = water_props['rho']
505
+
506
+ # Dielectric constant derivatives (simplified approximations)
507
+ deps_dT = -np.gradient(epsilon) / np.gradient(T) if len(T) > 1 else np.zeros_like(T)
508
+ deps_dP = np.zeros_like(T) # Would need pressure derivative
509
+
510
+ # Born functions (simplified)
511
+ QBorn = 1.0 / epsilon
512
+ XBorn = deps_dT / epsilon**2
513
+ YBorn = np.gradient(XBorn) / np.gradient(T) if len(T) > 1 else np.zeros_like(T)
514
+ NBorn = deps_dP / epsilon**2
515
+ UBorn = np.zeros_like(T) # Second derivative term
516
+
517
+ return {
518
+ 'QBorn': QBorn,
519
+ 'XBorn': XBorn,
520
+ 'YBorn': YBorn,
521
+ 'NBorn': NBorn,
522
+ 'UBorn': UBorn
523
+ }