pychnosz 1.1.12__cp310-cp310-macosx_15_0_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.
- pychnosz/.dylibs/libgcc_s.1.1.dylib +0 -0
- pychnosz/.dylibs/libgfortran.5.dylib +0 -0
- pychnosz/.dylibs/libquadmath.0.dylib +0 -0
- pychnosz/__init__.py +129 -0
- pychnosz/_version.py +34 -0
- pychnosz/biomolecules/__init__.py +29 -0
- pychnosz/biomolecules/ionize_aa.py +197 -0
- pychnosz/biomolecules/proteins.py +595 -0
- pychnosz/core/__init__.py +46 -0
- pychnosz/core/affinity.py +1256 -0
- pychnosz/core/animation.py +593 -0
- pychnosz/core/balance.py +334 -0
- pychnosz/core/basis.py +716 -0
- pychnosz/core/diagram.py +3336 -0
- pychnosz/core/equilibrate.py +813 -0
- pychnosz/core/equilibrium.py +554 -0
- pychnosz/core/info.py +821 -0
- pychnosz/core/retrieve.py +364 -0
- pychnosz/core/speciation.py +580 -0
- pychnosz/core/species.py +599 -0
- pychnosz/core/subcrt.py +1696 -0
- pychnosz/core/thermo.py +593 -0
- pychnosz/core/unicurve.py +1226 -0
- pychnosz/data/__init__.py +11 -0
- pychnosz/data/add_obigt.py +327 -0
- pychnosz/data/extdata/Berman/BDat17_2017.csv +2 -0
- pychnosz/data/extdata/Berman/Ber88_1988.csv +68 -0
- pychnosz/data/extdata/Berman/Ber90_1990.csv +5 -0
- pychnosz/data/extdata/Berman/DS10_2010.csv +6 -0
- pychnosz/data/extdata/Berman/FDM+14_2014.csv +2 -0
- pychnosz/data/extdata/Berman/Got04_2004.csv +5 -0
- pychnosz/data/extdata/Berman/JUN92_1992.csv +3 -0
- pychnosz/data/extdata/Berman/SHD91_1991.csv +12 -0
- pychnosz/data/extdata/Berman/VGT92_1992.csv +2 -0
- pychnosz/data/extdata/Berman/VPT01_2001.csv +3 -0
- pychnosz/data/extdata/Berman/VPV05_2005.csv +2 -0
- pychnosz/data/extdata/Berman/ZS92_1992.csv +11 -0
- pychnosz/data/extdata/Berman/sympy.R +99 -0
- pychnosz/data/extdata/Berman/testing/BA96.bib +12 -0
- pychnosz/data/extdata/Berman/testing/BA96_Berman.csv +21 -0
- pychnosz/data/extdata/Berman/testing/BA96_OBIGT.csv +21 -0
- pychnosz/data/extdata/Berman/testing/BA96_refs.csv +6 -0
- pychnosz/data/extdata/OBIGT/AD.csv +25 -0
- pychnosz/data/extdata/OBIGT/Berman_cr.csv +93 -0
- pychnosz/data/extdata/OBIGT/DEW.csv +211 -0
- pychnosz/data/extdata/OBIGT/H2O_aq.csv +4 -0
- pychnosz/data/extdata/OBIGT/SLOP98.csv +411 -0
- pychnosz/data/extdata/OBIGT/SUPCRT92.csv +178 -0
- pychnosz/data/extdata/OBIGT/inorganic_aq.csv +729 -0
- pychnosz/data/extdata/OBIGT/inorganic_cr.csv +273 -0
- pychnosz/data/extdata/OBIGT/inorganic_gas.csv +20 -0
- pychnosz/data/extdata/OBIGT/organic_aq.csv +1104 -0
- pychnosz/data/extdata/OBIGT/organic_cr.csv +481 -0
- pychnosz/data/extdata/OBIGT/organic_gas.csv +268 -0
- pychnosz/data/extdata/OBIGT/organic_liq.csv +533 -0
- pychnosz/data/extdata/OBIGT/testing/GEMSFIT.csv +43 -0
- pychnosz/data/extdata/OBIGT/testing/IGEM.csv +17 -0
- pychnosz/data/extdata/OBIGT/testing/Sandia.csv +8 -0
- pychnosz/data/extdata/OBIGT/testing/SiO2.csv +4 -0
- pychnosz/data/extdata/misc/AD03_Fig1a.csv +69 -0
- pychnosz/data/extdata/misc/AD03_Fig1b.csv +43 -0
- pychnosz/data/extdata/misc/AD03_Fig1c.csv +89 -0
- pychnosz/data/extdata/misc/AD03_Fig1d.csv +30 -0
- pychnosz/data/extdata/misc/BZA10.csv +5 -0
- pychnosz/data/extdata/misc/HW97_Cp.csv +90 -0
- pychnosz/data/extdata/misc/HWM96_V.csv +229 -0
- pychnosz/data/extdata/misc/LA19_test.csv +7 -0
- pychnosz/data/extdata/misc/Mer75_Table4.csv +42 -0
- pychnosz/data/extdata/misc/OBIGT_check.csv +423 -0
- pychnosz/data/extdata/misc/PM90.csv +7 -0
- pychnosz/data/extdata/misc/RH95.csv +23 -0
- pychnosz/data/extdata/misc/RH98_Table15.csv +17 -0
- pychnosz/data/extdata/misc/SC10_Rainbow.csv +19 -0
- pychnosz/data/extdata/misc/SK95.csv +55 -0
- pychnosz/data/extdata/misc/SOJSH.csv +61 -0
- pychnosz/data/extdata/misc/SS98_Fig5a.csv +81 -0
- pychnosz/data/extdata/misc/SS98_Fig5b.csv +84 -0
- pychnosz/data/extdata/misc/TKSS14_Fig2.csv +25 -0
- pychnosz/data/extdata/misc/bluered.txt +1000 -0
- pychnosz/data/extdata/protein/Cas/Cas_aa.csv +177 -0
- pychnosz/data/extdata/protein/Cas/Cas_uniprot.csv +186 -0
- pychnosz/data/extdata/protein/Cas/download.R +34 -0
- pychnosz/data/extdata/protein/Cas/mkaa.R +34 -0
- pychnosz/data/extdata/protein/POLG.csv +12 -0
- pychnosz/data/extdata/protein/TBD+05.csv +393 -0
- pychnosz/data/extdata/protein/TBD+05_aa.csv +393 -0
- pychnosz/data/extdata/protein/rubisco.csv +28 -0
- pychnosz/data/extdata/protein/rubisco.fasta +239 -0
- pychnosz/data/extdata/protein/rubisco_aa.csv +28 -0
- pychnosz/data/extdata/src/H2O92D.f.orig +3457 -0
- pychnosz/data/extdata/src/README.txt +5 -0
- pychnosz/data/extdata/taxonomy/names.dmp +215 -0
- pychnosz/data/extdata/taxonomy/nodes.dmp +63 -0
- pychnosz/data/extdata/thermo/Bdot_acirc.csv +60 -0
- pychnosz/data/extdata/thermo/buffer.csv +40 -0
- pychnosz/data/extdata/thermo/element.csv +135 -0
- pychnosz/data/extdata/thermo/groups.csv +6 -0
- pychnosz/data/extdata/thermo/opt.csv +2 -0
- pychnosz/data/extdata/thermo/protein.csv +506 -0
- pychnosz/data/extdata/thermo/refs.csv +343 -0
- pychnosz/data/extdata/thermo/stoich.csv.xz +0 -0
- pychnosz/data/loader.py +431 -0
- pychnosz/data/mod_obigt.py +322 -0
- pychnosz/data/obigt.py +471 -0
- pychnosz/data/worm.py +228 -0
- pychnosz/fortran/.gitignore +6 -0
- pychnosz/fortran/__init__.py +16 -0
- pychnosz/fortran/h2o92.dylib +0 -0
- pychnosz/fortran/h2o92_interface.py +527 -0
- pychnosz/geochemistry/__init__.py +21 -0
- pychnosz/geochemistry/minerals.py +514 -0
- pychnosz/geochemistry/redox.py +500 -0
- pychnosz/models/__init__.py +47 -0
- pychnosz/models/archer_wang.py +165 -0
- pychnosz/models/berman.py +309 -0
- pychnosz/models/cgl.py +381 -0
- pychnosz/models/dew.py +997 -0
- pychnosz/models/hkf.py +523 -0
- pychnosz/models/hkf_helpers.py +231 -0
- pychnosz/models/iapws95.py +1113 -0
- pychnosz/models/supcrt92_fortran.py +238 -0
- pychnosz/models/water.py +480 -0
- pychnosz/utils/__init__.py +27 -0
- pychnosz/utils/expression.py +1074 -0
- pychnosz/utils/formula.py +830 -0
- pychnosz/utils/formula_ox.py +227 -0
- pychnosz/utils/reset.py +33 -0
- pychnosz/utils/units.py +259 -0
- pychnosz-1.1.12.dist-info/METADATA +197 -0
- pychnosz-1.1.12.dist-info/RECORD +133 -0
- pychnosz-1.1.12.dist-info/WHEEL +5 -0
- pychnosz-1.1.12.dist-info/licenses/LICENSE.txt +19 -0
- pychnosz-1.1.12.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
|
+
}
|