DiadFit 0.0.88__py3-none-any.whl → 1.0.0__py3-none-any.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.
- DiadFit/CO2_EOS.py +1320 -5
- DiadFit/CO2_in_bubble_error.py +91 -19
- DiadFit/__init__.py +1 -1
- DiadFit/_version.py +1 -1
- DiadFit/densimeters.py +9 -5
- DiadFit/diads.py +2 -0
- DiadFit/error_propagation.py +407 -152
- {DiadFit-0.0.88.dist-info → DiadFit-1.0.0.dist-info}/METADATA +1 -1
- {DiadFit-0.0.88.dist-info → DiadFit-1.0.0.dist-info}/RECORD +11 -12
- DiadFit/CO2_H2O_EOS.py +0 -1255
- {DiadFit-0.0.88.dist-info → DiadFit-1.0.0.dist-info}/WHEEL +0 -0
- {DiadFit-0.0.88.dist-info → DiadFit-1.0.0.dist-info}/top_level.txt +0 -0
DiadFit/CO2_EOS.py
CHANGED
@@ -9,8 +9,7 @@ from scipy.optimize import minimize
|
|
9
9
|
from pathlib import Path
|
10
10
|
from pickle import load
|
11
11
|
import pickle
|
12
|
-
|
13
|
-
|
12
|
+
import math
|
14
13
|
DiadFit_dir=Path(__file__).parent
|
15
14
|
|
16
15
|
|
@@ -329,7 +328,7 @@ def calculate_rho_for_P_T(P_kbar, T_K, EOS='SW96'):
|
|
329
328
|
Temperature in Kelvin
|
330
329
|
|
331
330
|
EOS: str
|
332
|
-
'SW96' for Span and Wanger (1996),
|
331
|
+
'SW96' for Span and Wanger (1996), 'SP94' for Sterner and Pitzer (1994)
|
333
332
|
|
334
333
|
|
335
334
|
Returns
|
@@ -348,11 +347,15 @@ def calculate_rho_for_P_T(P_kbar, T_K, EOS='SW96'):
|
|
348
347
|
CO2_dens_gcm3=calculate_rho_for_P_T_SP94(T_K=T_K, P_kbar=P_kbar)
|
349
348
|
|
350
349
|
|
350
|
+
|
351
|
+
|
351
352
|
return pd.Series(CO2_dens_gcm3)
|
352
353
|
|
353
354
|
|
354
355
|
|
355
356
|
|
357
|
+
|
358
|
+
|
356
359
|
# Function for Span and Wanger (1996)
|
357
360
|
|
358
361
|
def calculate_rho_for_P_T_SW96(P_kbar, T_K):
|
@@ -427,7 +430,8 @@ def calculate_P_for_rho_T(CO2_dens_gcm3, T_K, EOS='SW96', Sample_ID=None):
|
|
427
430
|
Temperature in Kelvin
|
428
431
|
|
429
432
|
EOS: str
|
430
|
-
'SW96' for Span and Wanger (1996),
|
433
|
+
'SW96' for Span and Wanger (1996), 'SP94' for Sterner and Pitzer (1994),
|
434
|
+
or 'DZ06' for Pure CO2 from Duan and Zhang (2006)
|
431
435
|
|
432
436
|
Sample_ID: str, pd.Series
|
433
437
|
Sample ID to be appended onto output dataframe
|
@@ -444,8 +448,12 @@ def calculate_P_for_rho_T(CO2_dens_gcm3, T_K, EOS='SW96', Sample_ID=None):
|
|
444
448
|
df=calculate_P_for_rho_T_SW96(CO2_dens_gcm3=CO2_dens_gcm3, T_K=T_K)
|
445
449
|
elif EOS=='SP94':
|
446
450
|
df=calculate_P_for_rho_T_SP94(CO2_dens_gcm3=CO2_dens_gcm3, T_K=T_K)
|
451
|
+
elif EOS=='DZ06':
|
452
|
+
df=calculate_P_for_rho_T_DZ06(CO2_dens_gcm3=CO2_dens_gcm3, T_K=T_K)
|
453
|
+
|
454
|
+
|
447
455
|
else:
|
448
|
-
raise TypeError('Please choose either SP94 or
|
456
|
+
raise TypeError('Please choose either SP94, SW96, or DZ06 as an EOS')
|
449
457
|
|
450
458
|
if Sample_ID is not None:
|
451
459
|
df['Sample_ID']=Sample_ID
|
@@ -457,6 +465,40 @@ def calculate_P_for_rho_T(CO2_dens_gcm3, T_K, EOS='SW96', Sample_ID=None):
|
|
457
465
|
|
458
466
|
# Calculating P for a given density and Temperature using Coolprop
|
459
467
|
|
468
|
+
def calculate_P_for_rho_T_DZ06(CO2_dens_gcm3, T_K):
|
469
|
+
""" This function calculates P in kbar for a specified CO2 density in g/cm3 and a known T (in K) for the pure CO2 Duan and Zhang (2006) EOS (e.g. XH2O=0)
|
470
|
+
|
471
|
+
Parameters
|
472
|
+
---------------------
|
473
|
+
CO2_dens_gcm3: int, float, pd.Series, np.array
|
474
|
+
CO2 density in g/cm3
|
475
|
+
|
476
|
+
T_K: int, float, pd.Series, np.array
|
477
|
+
Temperature in Kelvin
|
478
|
+
|
479
|
+
|
480
|
+
Returns
|
481
|
+
--------------------
|
482
|
+
pd.DataFrame
|
483
|
+
Pressure in kbar. MPa and input parameters
|
484
|
+
|
485
|
+
"""
|
486
|
+
P_MPa=calculate_Pressure_DZ2006(density=CO2_dens_gcm3, T_K=T_K, XH2O=0)/10
|
487
|
+
|
488
|
+
|
489
|
+
|
490
|
+
df=pd.DataFrame(data={'P_kbar': P_MPa/100,
|
491
|
+
'P_MPa': P_MPa,
|
492
|
+
'T_K': T_K,
|
493
|
+
'CO2_dens_gcm3': CO2_dens_gcm3
|
494
|
+
|
495
|
+
}, index=[0])
|
496
|
+
|
497
|
+
return df
|
498
|
+
|
499
|
+
|
500
|
+
|
501
|
+
|
460
502
|
def calculate_P_for_rho_T_SW96(CO2_dens_gcm3, T_K):
|
461
503
|
""" This function calculates P in kbar for a specified CO2 density in g/cm3 and a known T (in K) for the Span and Wanger (1996) EOS
|
462
504
|
|
@@ -867,6 +909,1279 @@ def calculate_SP1994_Temp(CO2_dens_gcm3, target_pressure_MPa):
|
|
867
909
|
|
868
910
|
|
869
911
|
|
912
|
+
## Combined CO2 and H2O-CO2 EOS files to avoid circular imports for pure DZ EOS.
|
913
|
+
|
914
|
+
|
915
|
+
# Set up constants.
|
916
|
+
Tc1 = 647.25
|
917
|
+
Pc1 = 221.19
|
918
|
+
Tc2 = 301.1282
|
919
|
+
Pc2 = 73.773
|
920
|
+
|
921
|
+
# Set up low pressure and high pressure parameters for CO2.
|
922
|
+
|
923
|
+
aL1 = [0] * 16 # Assuming the array is 1-indexed like in C.
|
924
|
+
#So we dont need to adjust everything
|
925
|
+
|
926
|
+
aL1[1] = 4.38269941 / 10**2
|
927
|
+
aL1[2] = -1.68244362 / 10**1
|
928
|
+
aL1[3] = -2.36923373 / 10**1
|
929
|
+
aL1[4] = 1.13027462 / 10**2
|
930
|
+
aL1[5] = -7.67764181 / 10**2
|
931
|
+
aL1[6] = 9.71820593 / 10**2
|
932
|
+
aL1[7] = 6.62674916 / 10**5
|
933
|
+
aL1[8] = 1.06637349 / 10**3
|
934
|
+
aL1[9] = -1.23265258 / 10**3
|
935
|
+
aL1[10] = -8.93953948 / 10**6
|
936
|
+
aL1[11] = -3.88124606 / 10**5
|
937
|
+
aL1[12] = 5.61510206 / 10**5
|
938
|
+
aL1[13] = 7.51274488 / 10**3 # alpha for H2O
|
939
|
+
aL1[14] = 2.51598931 # beta for H2O
|
940
|
+
aL1[15] = 3.94 / 10**2 # gamma for H2O
|
941
|
+
|
942
|
+
# Higher pressure parameters - 0.2- 1 GPa
|
943
|
+
aH1 = [0] * 16 # Assuming the array is 1-indexed like in C
|
944
|
+
|
945
|
+
aH1[1] = 4.68071541 / 10**2
|
946
|
+
aH1[2] = -2.81275941 / 10**1
|
947
|
+
aH1[3] = -2.43926365 / 10**1
|
948
|
+
aH1[4] = 1.10016958 / 10**2
|
949
|
+
aH1[5] = -3.86603525 / 10**2
|
950
|
+
aH1[6] = 9.30095461 / 10**2
|
951
|
+
aH1[7] = -1.15747171 / 10**5
|
952
|
+
aH1[8] = 4.19873848 / 10**4
|
953
|
+
aH1[9] = -5.82739501 / 10**4
|
954
|
+
aH1[10] = 1.00936000 / 10**6
|
955
|
+
aH1[11] = -1.01713593 / 10**5
|
956
|
+
aH1[12] = 1.63934213 / 10**5
|
957
|
+
aH1[13] = -4.49505919 / 10**2 # alpha for H2O
|
958
|
+
aH1[14] = -3.15028174 / 10**1 # beta for H2O
|
959
|
+
aH1[15] = 1.25 / 10**2 # gamma for H2O
|
960
|
+
|
961
|
+
|
962
|
+
# Low presure CO2 parameters.
|
963
|
+
|
964
|
+
aL2 = [0] * 16 # Assuming the array is 1-indexed like in C
|
965
|
+
|
966
|
+
aL2[1] = 1.14400435 / 10**1
|
967
|
+
aL2[2] = -9.38526684 / 10**1
|
968
|
+
aL2[3] = 7.21857006 / 10**1
|
969
|
+
aL2[4] = 8.81072902 / 10**3
|
970
|
+
aL2[5] = 6.36473911 / 10**2
|
971
|
+
aL2[6] = -7.70822213 / 10**2
|
972
|
+
aL2[7] = 9.01506064 / 10**4
|
973
|
+
aL2[8] = -6.81834166 / 10**3
|
974
|
+
aL2[9] = 7.32364258 / 10**3
|
975
|
+
aL2[10] = -1.10288237 / 10**4
|
976
|
+
aL2[11] = 1.26524193 / 10**3
|
977
|
+
aL2[12] = -1.49730823 / 10**3
|
978
|
+
aL2[13] = 7.81940730 / 10**3 # alpha for CO2
|
979
|
+
aL2[14] = -4.22918013 # beta for CO2
|
980
|
+
aL2[15] = 1.585 / 10**1 # gamma for CO2
|
981
|
+
|
982
|
+
|
983
|
+
|
984
|
+
# High pressure CO2 parameters.
|
985
|
+
aH2 = [0] * 16 # Assuming the array is 1-indexed like in C
|
986
|
+
aH2[1] = 5.72573440 / 10**3
|
987
|
+
aH2[2] = 7.94836769
|
988
|
+
aH2[3] = -3.84236281 * 10.0
|
989
|
+
aH2[4] = 3.71600369 / 10**2
|
990
|
+
aH2[5] = -1.92888994
|
991
|
+
aH2[6] = 6.64254770
|
992
|
+
aH2[7] = -7.02203950 / 10**6
|
993
|
+
aH2[8] = 1.77093234 / 10**2
|
994
|
+
aH2[9] = -4.81892026 / 10**2
|
995
|
+
aH2[10] = 3.88344869 / 10**6
|
996
|
+
aH2[11] = -5.54833167 / 10**4
|
997
|
+
aH2[12] = 1.70489748 / 10**3
|
998
|
+
aH2[13] = -4.13039220 / 10**1 # alpha for CO2
|
999
|
+
aH2[14] = -8.47988634 # beta for CO2
|
1000
|
+
aH2[15] = 2.800 / 10**2 # gamma for CO2
|
1001
|
+
|
1002
|
+
|
1003
|
+
|
1004
|
+
import pandas as pd
|
1005
|
+
import numpy as np
|
1006
|
+
|
1007
|
+
def ensure_series(a, b, c):
|
1008
|
+
# Determine the target length
|
1009
|
+
lengths = [len(a) if isinstance(a, (pd.Series, np.ndarray)) else None,
|
1010
|
+
len(b) if isinstance(b, (pd.Series, np.ndarray)) else None,
|
1011
|
+
len(c) if isinstance(c, (pd.Series, np.ndarray)) else None]
|
1012
|
+
lengths = [l for l in lengths if l is not None]
|
1013
|
+
target_length = max(lengths) if lengths else 1
|
1014
|
+
|
1015
|
+
# Convert each input to a Series of the target length
|
1016
|
+
if not isinstance(a, (pd.Series, np.ndarray)):
|
1017
|
+
a = pd.Series([a] * target_length)
|
1018
|
+
else:
|
1019
|
+
a = pd.Series(a)
|
1020
|
+
|
1021
|
+
if not isinstance(b, (pd.Series, np.ndarray)):
|
1022
|
+
b = pd.Series([b] * target_length)
|
1023
|
+
else:
|
1024
|
+
b = pd.Series(b)
|
1025
|
+
|
1026
|
+
if not isinstance(c, (pd.Series, np.ndarray)):
|
1027
|
+
c = pd.Series([c] * target_length)
|
1028
|
+
else:
|
1029
|
+
c = pd.Series(c)
|
1030
|
+
|
1031
|
+
return a, b, c
|
1032
|
+
|
1033
|
+
|
1034
|
+
def ensure_series_4(a, b, c, d):
|
1035
|
+
# Determine the target length
|
1036
|
+
lengths = [len(a) if isinstance(a, (pd.Series, np.ndarray)) else None,
|
1037
|
+
len(b) if isinstance(b, (pd.Series, np.ndarray)) else None,
|
1038
|
+
len(c) if isinstance(c, (pd.Series, np.ndarray)) else None,
|
1039
|
+
len(d) if isinstance(d, (pd.Series, np.ndarray)) else None]
|
1040
|
+
lengths = [l for l in lengths if l is not None]
|
1041
|
+
target_length = max(lengths) if lengths else 1
|
1042
|
+
|
1043
|
+
# Convert each input to a Series of the target length
|
1044
|
+
if not isinstance(a, (pd.Series, np.ndarray)):
|
1045
|
+
a = pd.Series([a] * target_length)
|
1046
|
+
else:
|
1047
|
+
a = pd.Series(a)
|
1048
|
+
|
1049
|
+
if not isinstance(b, (pd.Series, np.ndarray)):
|
1050
|
+
b = pd.Series([b] * target_length)
|
1051
|
+
else:
|
1052
|
+
b = pd.Series(b)
|
1053
|
+
|
1054
|
+
if not isinstance(c, (pd.Series, np.ndarray)):
|
1055
|
+
c = pd.Series([c] * target_length)
|
1056
|
+
else:
|
1057
|
+
c = pd.Series(c)
|
1058
|
+
|
1059
|
+
if not isinstance(d, (pd.Series, np.ndarray)):
|
1060
|
+
d = pd.Series([d] * target_length)
|
1061
|
+
else:
|
1062
|
+
d = pd.Series(d)
|
1063
|
+
|
1064
|
+
return a, b, c, d
|
1065
|
+
|
1066
|
+
|
1067
|
+
|
1068
|
+
## Pure EOS functions
|
1069
|
+
# First, we need the pure EOS
|
1070
|
+
def pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g):
|
1071
|
+
"""
|
1072
|
+
This function calculates the compressability factor for a pure EOS using the modified Lee-Kesler
|
1073
|
+
equation.
|
1074
|
+
|
1075
|
+
i=0 for H2O, i=1 for CO2.
|
1076
|
+
|
1077
|
+
You input a volume, and it returns the difference between the compresability factor, and that calculated at the input P, V and T_K.
|
1078
|
+
E.g. gives the residual so that you can iterate.
|
1079
|
+
"""
|
1080
|
+
CF = (1.0 + (B[i] * Vc[i] / V) + (C[i] * Vc[i] *
|
1081
|
+
Vc[i] / (V * V)) + (D[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
|
1082
|
+
(V * V * V * V)))
|
1083
|
+
|
1084
|
+
CF += ((E[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
|
1085
|
+
(V * V * V * V * V)))
|
1086
|
+
|
1087
|
+
CF += ((F[i] * Vc[i] * Vc[i] / (V * V)) *
|
1088
|
+
(b[i] + g[i] * Vc[i] * Vc[i] / (V * V)) *
|
1089
|
+
math.exp(-g[i] * Vc[i] * Vc[i] / (V * V)))
|
1090
|
+
|
1091
|
+
return CF - (P * V) / (83.14467 * TK)
|
1092
|
+
|
1093
|
+
def pureEOS_CF(i, V, P, B, C, D, E, F, Vc, TK, b, g):
|
1094
|
+
"""
|
1095
|
+
This function calculates the compressability factor for a pure EOS using the modified Lee-Kesler
|
1096
|
+
equation.
|
1097
|
+
|
1098
|
+
i=0 for H2O, i=1 for CO2.
|
1099
|
+
|
1100
|
+
You input a volume, and it returns the difference between the compresability factor, and that calculated at the input P, V and T_K.
|
1101
|
+
E.g. gives the residual so that you can iterate.
|
1102
|
+
"""
|
1103
|
+
CF = (1.0 + (B[i] * Vc[i] / V) + (C[i] * Vc[i] *
|
1104
|
+
Vc[i] / (V * V)) + (D[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
|
1105
|
+
(V * V * V * V)))
|
1106
|
+
|
1107
|
+
CF += ((E[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
|
1108
|
+
(V * V * V * V * V)))
|
1109
|
+
|
1110
|
+
CF += ((F[i] * Vc[i] * Vc[i] / (V * V)) *
|
1111
|
+
(b[i] + g[i] * Vc[i] * Vc[i] / (V * V)) *
|
1112
|
+
math.exp(-g[i] * Vc[i] * Vc[i] / (V * V)))
|
1113
|
+
|
1114
|
+
return CF
|
1115
|
+
|
1116
|
+
|
1117
|
+
# Volume iterative function using Netwon-Raphson method.
|
1118
|
+
def purevolume(i, V, P, B, C, D, E, F, Vc, TK, b, g):
|
1119
|
+
""" Using the pure EOS, this function solves for the best molar volume (in cm3/mol) using the pureEOS residual calculated above
|
1120
|
+
|
1121
|
+
It returns the volume.
|
1122
|
+
|
1123
|
+
"""
|
1124
|
+
for iter in range(1, 51):
|
1125
|
+
# Calculate the derivative of the pureEOS function at (V, P)
|
1126
|
+
diff = (pureEOS(i, V + 0.0001, P,B, C, D, E, F, Vc, TK, b, g) - pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g)) / 0.0001
|
1127
|
+
|
1128
|
+
# Update the volume using the Newton-Raphson method
|
1129
|
+
Vnew = V - pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g) / diff
|
1130
|
+
|
1131
|
+
# Check if the update is within the tolerance (0.000001)
|
1132
|
+
if abs(Vnew - V) <= 0.000001:
|
1133
|
+
break
|
1134
|
+
|
1135
|
+
# Update V for the next iteration
|
1136
|
+
V = Vnew
|
1137
|
+
|
1138
|
+
# Return the final estimated volume
|
1139
|
+
return V
|
1140
|
+
|
1141
|
+
def purepressure(i, V, P, TK):
|
1142
|
+
""" Using the pure EOS, this function solves for the best pressure (in bars) using the pureEOS residual calculated above
|
1143
|
+
|
1144
|
+
It returns the pressure in bars
|
1145
|
+
|
1146
|
+
"""
|
1147
|
+
for iter in range(1, 51):
|
1148
|
+
# Calculate the derivative of the pureEOS function at (V, P)
|
1149
|
+
k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
|
1150
|
+
|
1151
|
+
diff = (pureEOS(i, V, P + 0.0001, B, C, D, E, F, Vc, TK, b, g) - pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g)) / 0.0001
|
1152
|
+
|
1153
|
+
# Update the pressure using the Newton-Raphson method
|
1154
|
+
Pnew = P - pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g) / diff
|
1155
|
+
|
1156
|
+
# Dont allow negative solutions
|
1157
|
+
if Pnew < 0:
|
1158
|
+
Pnew = 30000
|
1159
|
+
|
1160
|
+
# Check if the update is within the tolerance (0.000001)
|
1161
|
+
if abs(Pnew - P) <= 0.000001:
|
1162
|
+
break
|
1163
|
+
|
1164
|
+
# Update P for the next iteration
|
1165
|
+
P = Pnew
|
1166
|
+
|
1167
|
+
# Return the final estimated pressure
|
1168
|
+
return P
|
1169
|
+
|
1170
|
+
|
1171
|
+
|
1172
|
+
|
1173
|
+
def mol_vol_to_density(mol_vol, XH2O):
|
1174
|
+
""" Converts molar volume (cm3/mol) to density (g/cm3) for a given XH2O"""
|
1175
|
+
density=((1-XH2O)*44 + (XH2O)*18)/mol_vol
|
1176
|
+
return density
|
1177
|
+
|
1178
|
+
def pure_lnphi(i, Z, B, Vc, V, C, D, E, F, g, b):
|
1179
|
+
"""
|
1180
|
+
This function calculates the fugacity coefficient (kbar) from the equation of state for a pure fluid
|
1181
|
+
|
1182
|
+
"""
|
1183
|
+
lnph = Z[i] - 1.0 - math.log(Z[i]) + (B[i] * Vc[i] / V[i]) + (C[i] * Vc[i] * Vc[i] / (2.0 * V[i] * V[i]))
|
1184
|
+
lnph += (D[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] / (4.0 * V[i] * V[i] * V[i] * V[i]))
|
1185
|
+
lnph += (E[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] / (5.0 * V[i] * V[i] * V[i] * V[i] * V[i]))
|
1186
|
+
lnph += (F[i] / (2.0 * g[i])) * (b[i] + 1.0 - (b[i] + 1.0 + g[i] * Vc[i] * Vc[i] / (V[i] * V[i])) * math.exp(-g[i] * Vc[i] * Vc[i] / (V[i] * V[i])))
|
1187
|
+
|
1188
|
+
return lnph
|
1189
|
+
|
1190
|
+
|
1191
|
+
|
1192
|
+
## Mixing between species
|
1193
|
+
def cbrt_calc(x):
|
1194
|
+
"""
|
1195
|
+
This function calculates the cubic root that can deal with negative numbers.
|
1196
|
+
"""
|
1197
|
+
if x >= 0:
|
1198
|
+
return math.pow(x, 1/3)
|
1199
|
+
else:
|
1200
|
+
return -math.pow(-x, 1/3)
|
1201
|
+
|
1202
|
+
|
1203
|
+
def mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK):
|
1204
|
+
""" This function is like the one for the pureEOS. It calculates the compressability factor, and
|
1205
|
+
then calculates the compressability factor based on the P-V-T you entered. It returns the residual between those two values.
|
1206
|
+
"""
|
1207
|
+
CF = 1.0 + (BVc / V) + (CVc2 / (V * V)) + (DVc4 / (V * V * V * V)) + (EVc5 / (V * V * V * V * V))
|
1208
|
+
CF += (FVc2 / (V * V)) * (bmix + gVc2 / (V * V)) * np.exp(-gVc2 / (V * V))
|
1209
|
+
|
1210
|
+
return CF - (P * V) / (83.14467 * TK)
|
1211
|
+
|
1212
|
+
|
1213
|
+
def mixEOS_CF(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK):
|
1214
|
+
""" This function is like the one for the pureEOS. It calculates the compressability factor, and
|
1215
|
+
then calculates that based on the P-V-T you entered. It does not return the residual
|
1216
|
+
"""
|
1217
|
+
CF = 1.0 + (BVc / V) + (CVc2 / (V * V)) + (DVc4 / (V * V * V * V)) + (EVc5 / (V * V * V * V * V))
|
1218
|
+
CF += (FVc2 / (V * V)) * (bmix + gVc2 / (V * V)) * np.exp(-gVc2 / (V * V))
|
1219
|
+
|
1220
|
+
return CF
|
1221
|
+
|
1222
|
+
|
1223
|
+
def mixvolume(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK):
|
1224
|
+
""" This function iterates in volume space to get the best match volume (cm3/mol) to the entered pressure using the mixEOS function above.
|
1225
|
+
|
1226
|
+
"""
|
1227
|
+
for iter in range(1, 51):
|
1228
|
+
diff = ((mixEOS(V + 0.0001, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)
|
1229
|
+
- mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)) / 0.0001)
|
1230
|
+
Vnew = V - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK) / diff
|
1231
|
+
if abs(Vnew - V) <= 0.000001:
|
1232
|
+
break
|
1233
|
+
V = Vnew
|
1234
|
+
|
1235
|
+
return V
|
1236
|
+
|
1237
|
+
import warnings as w
|
1238
|
+
|
1239
|
+
## We are going to have to use a look up table to help the netwon raphson converge.
|
1240
|
+
|
1241
|
+
# Load the lookup table from the CSV file
|
1242
|
+
DiadFit_dir=Path(__file__).parent
|
1243
|
+
file_str='lookup_table_noneg.csv'
|
1244
|
+
dz06_lookuptable=pd.read_csv(DiadFit_dir/file_str)
|
1245
|
+
#df = pd.read_csv('lookup_table_noneg.csv')
|
1246
|
+
|
1247
|
+
|
1248
|
+
|
1249
|
+
|
1250
|
+
|
1251
|
+
def get_initial_guess(V_target, T_K_target, XH2O_target):
|
1252
|
+
# Calculate the Euclidean distance from the target point to all points in the table
|
1253
|
+
# We normalize each dimension by its range to give equal weight to all parameters
|
1254
|
+
df=dz06_lookuptable
|
1255
|
+
|
1256
|
+
# code to find best value
|
1257
|
+
|
1258
|
+
P_range = df['P_kbar'].max() - df['P_kbar'].min()
|
1259
|
+
T_K_range = df['T_K'].max() - df['T_K'].min()
|
1260
|
+
XH2O_range = df['XH2O'].max() - df['XH2O'].min()
|
1261
|
+
V_range = df['V'].max() - df['V'].min()
|
1262
|
+
|
1263
|
+
# Calculate normalized distances
|
1264
|
+
distances = np.sqrt(
|
1265
|
+
((df['P_kbar'] - df['P_kbar'].mean()) / P_range) ** 2 +
|
1266
|
+
((df['T_K'] - T_K_target) / T_K_range) ** 2 +
|
1267
|
+
((df['XH2O'] - XH2O_target) / XH2O_range) ** 2 +
|
1268
|
+
((df['V'] - V_target) / V_range) ** 2
|
1269
|
+
)
|
1270
|
+
|
1271
|
+
# Drop NaN values from distances
|
1272
|
+
non_nan_distances = distances.dropna()
|
1273
|
+
|
1274
|
+
# Check if all distances are NaN
|
1275
|
+
if non_nan_distances.empty:
|
1276
|
+
return 10
|
1277
|
+
|
1278
|
+
# Find the index of the closest row in the DataFrame
|
1279
|
+
closest_index = non_nan_distances.idxmin()
|
1280
|
+
|
1281
|
+
# Retrieve the P_kbar value from the closest row
|
1282
|
+
initial_guess_P = df.iloc[closest_index]['P_kbar']
|
1283
|
+
|
1284
|
+
return initial_guess_P
|
1285
|
+
|
1286
|
+
|
1287
|
+
|
1288
|
+
|
1289
|
+
def mixpressure(P, V, TK, Y):
|
1290
|
+
""" This function iterates in pressure space to get the best match in bars to the entered volume in cm3/mol using the mixEOS function above.
|
1291
|
+
|
1292
|
+
"""
|
1293
|
+
|
1294
|
+
|
1295
|
+
for iter in range(1, 51):
|
1296
|
+
k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
|
1297
|
+
Bij, Vcij, BVc_prm, BVc, Cijk, Vcijk, CVc2_prm, CVc2, Dijklm, Vcijklm, DVc4_prm, DVc4, Eijklmn, Vcijklmn, EVc5_prm, EVc5, Fij, FVc2_prm, FVc2, bmix, b_prm, gijk, gVc2_prm, gVc2=mixing_rules(B, C,D, E, F, Vc, Y, b, g, k1_temperature, k2_temperature, k3_temperature)
|
1298
|
+
|
1299
|
+
diff = ((mixEOS(V, P + 0.0001, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)
|
1300
|
+
- mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)) / 0.0001)
|
1301
|
+
Pnew = P - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK) / diff
|
1302
|
+
|
1303
|
+
|
1304
|
+
|
1305
|
+
|
1306
|
+
if abs(Pnew - P) <= 0.000001:
|
1307
|
+
break
|
1308
|
+
|
1309
|
+
P = Pnew
|
1310
|
+
|
1311
|
+
return P
|
1312
|
+
|
1313
|
+
# # Dont allow negative solutions
|
1314
|
+
# if Pnew<0:
|
1315
|
+
# Pnew = 3000
|
1316
|
+
|
1317
|
+
# if Pnew < 10000 and Pnew>0 and V<50:
|
1318
|
+
# w.warn('Sometimes the adapted Newton Raphson method will find a second root at lower (or negative pressure). This initially found a root at P=' + str(np.round(Pnew, 2)) + ', V=' + str(np.round(V)) + '. The algorithm has started its search again at P=3000 bars. Double check your results make sense')
|
1319
|
+
#
|
1320
|
+
# Pnew = 10000 # Replace 0.0001 with a small positive value that makes sense for your system
|
1321
|
+
#
|
1322
|
+
|
1323
|
+
|
1324
|
+
# def mixpressure(P_init, V, TK, Y, max_restarts=3):
|
1325
|
+
# """This function iterates in pressure space to get the best match to the entered volume using the mixEOS function above."""
|
1326
|
+
#
|
1327
|
+
# restarts = 0
|
1328
|
+
# while restarts <= max_restarts:
|
1329
|
+
# P = P_init
|
1330
|
+
# for iter in range(1, 51):
|
1331
|
+
# # Your EOS parameters and mixing rules calculations here
|
1332
|
+
#
|
1333
|
+
# diff = ((mixEOS(V, P + 0.0001, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK) - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)) / 0.0001)
|
1334
|
+
# Pnew = P - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK) / diff
|
1335
|
+
#
|
1336
|
+
# # Don't allow unrealistic solutions but provide a chance to reset
|
1337
|
+
# if Pnew < 5000 and V > 10:
|
1338
|
+
# warnings.warn('Root forced above 5000 bars due to conditions, attempting restart...')
|
1339
|
+
# Pnew = 5000 # Force the pressure up due to your condition
|
1340
|
+
# break # Break out of the current iteration loop to allow for a restart
|
1341
|
+
#
|
1342
|
+
# if abs(Pnew - P) <= 0.000001: # Convergence criterion
|
1343
|
+
# return Pnew # Return the converged value
|
1344
|
+
# P = Pnew
|
1345
|
+
#
|
1346
|
+
# restarts += 1 # Increment the number of restarts attempted
|
1347
|
+
# P_init = 5000 # Set a new starting point that might be closer to the suspected real root
|
1348
|
+
#
|
1349
|
+
# warnings.warn('Max restarts reached, solution may not be optimal.')
|
1350
|
+
# return P # Return the last computed pressure if all restart attempts fail
|
1351
|
+
|
1352
|
+
|
1353
|
+
|
1354
|
+
|
1355
|
+
|
1356
|
+
|
1357
|
+
def mix_lnphi(i, Zmix, BVc_prm, CVc2_prm, DVc4_prm, EVc5_prm, FVc2_prm, FVc2, bmix, b_prm, gVc2, gVc2_prm, Vmix):
|
1358
|
+
""" This function calculates lnphi values"""
|
1359
|
+
lnph=0
|
1360
|
+
|
1361
|
+
lnph = -math.log(Zmix)
|
1362
|
+
lnph += (BVc_prm[i] / Vmix)
|
1363
|
+
lnph += (CVc2_prm[i] / (2.0 * Vmix ** 2))
|
1364
|
+
lnph += (DVc4_prm[i] / (4.0 * Vmix ** 4))
|
1365
|
+
lnph += (EVc5_prm[i] / (5.0 * Vmix ** 5))
|
1366
|
+
lnph += ((FVc2_prm[i] * bmix + b_prm[i] * FVc2) / (2 * gVc2)) * (1.0 - math.exp(-gVc2 / (Vmix ** 2)))
|
1367
|
+
lnph += ((FVc2_prm[i] * gVc2 + gVc2_prm[i] * FVc2 - FVc2 * bmix * (gVc2_prm[i] - gVc2)) / (2.0 * gVc2 ** 2)) * (1.0 - (gVc2 / (Vmix ** 2) + 1.0) * math.exp(-gVc2 / (Vmix ** 2)))
|
1368
|
+
lnph += -(((gVc2_prm[i] - gVc2) * FVc2) / (2 * gVc2 ** 2)) * (2.0 - (((gVc2 ** 2) / (Vmix ** 4)) + (2.0 * gVc2 / (Vmix ** 2)) + 2.0) * math.exp(-gVc2 / (Vmix ** 2)))
|
1369
|
+
|
1370
|
+
|
1371
|
+
return lnph
|
1372
|
+
|
1373
|
+
|
1374
|
+
|
1375
|
+
def mix_fugacity_ind(*, P_kbar, T_K, XH2O, Vmix):
|
1376
|
+
""" This function calculates fugacity for a single sample.
|
1377
|
+
It returns the activity of each component (fugacity/fugacity in pure component)
|
1378
|
+
|
1379
|
+
|
1380
|
+
"""
|
1381
|
+
|
1382
|
+
P=P_kbar*1000
|
1383
|
+
TK=T_K
|
1384
|
+
XCO2=1-XH2O
|
1385
|
+
Y = [0] * 2
|
1386
|
+
Y[0]=XH2O
|
1387
|
+
Y[1]=XCO2
|
1388
|
+
|
1389
|
+
# Calculate the constants you neeed
|
1390
|
+
k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
|
1391
|
+
|
1392
|
+
|
1393
|
+
lnphi2kbL = [0.0, 0.0]
|
1394
|
+
lnphi2kbH = [0.0, 0.0]
|
1395
|
+
|
1396
|
+
lnphi = [0.0, 0.0]
|
1397
|
+
phi_mix = [0.0, 0.0]
|
1398
|
+
activity = [0.0, 0.0]
|
1399
|
+
f = [0.0, 0.0]
|
1400
|
+
|
1401
|
+
# Calculate at pressure of interest
|
1402
|
+
Z_pure = [0.0, 0.0]
|
1403
|
+
V_pure = [0.0, 0.0]
|
1404
|
+
|
1405
|
+
|
1406
|
+
# Initial guess for volumne
|
1407
|
+
|
1408
|
+
if P<=2000:
|
1409
|
+
Vguess=1000
|
1410
|
+
elif P>20000:
|
1411
|
+
Vguess=10
|
1412
|
+
else:
|
1413
|
+
Vguess=100
|
1414
|
+
|
1415
|
+
V_pure[1]=purevolume(1, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
|
1416
|
+
V_pure[0]=purevolume(0, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
|
1417
|
+
Z_pure[0]=P*V_pure[0]/(83.14467*TK)
|
1418
|
+
Z_pure[1]=P*V_pure[1]/(83.14467*TK)
|
1419
|
+
|
1420
|
+
|
1421
|
+
#H2O pure
|
1422
|
+
lnphi0=pure_lnphi(0, Z_pure, B, Vc, V_pure, C, D, E, F, g, b)
|
1423
|
+
#CO2 pure
|
1424
|
+
lnphi1=pure_lnphi(1, Z_pure, B, Vc, V_pure, C, D, E, F, g, b)
|
1425
|
+
|
1426
|
+
|
1427
|
+
# Funny maths you have to do incase P>2000 bars
|
1428
|
+
|
1429
|
+
|
1430
|
+
# First, calculate parameters with low pressure coefficients
|
1431
|
+
k1_temperature_LP, k2_temperature_LP, k3_temperature_LP, a1_LP, a2_LP, g_LP, b_LP, Vc_LP, B_LP, C_LP, D_LP, E_LP, F_LP, Vguess=get_EOS_params(500, TK)
|
1432
|
+
Z_pure_LP_2000 = [0.0, 0.0]
|
1433
|
+
V_pure_LP_2000 = [0.0, 0.0]
|
1434
|
+
V_pure_LP_2000[0]=purevolume(0, 100, 2000, B_LP, C_LP, D_LP, E_LP, F_LP, Vc_LP, TK, b_LP, g_LP)
|
1435
|
+
V_pure_LP_2000[1]=purevolume(1, 100, 2000, B_LP, C_LP, D_LP, E_LP, F_LP, Vc_LP, TK, b_LP, g_LP)
|
1436
|
+
|
1437
|
+
Z_pure_LP_2000[0]=2000.0*V_pure_LP_2000[0]/(83.14467*TK)
|
1438
|
+
Z_pure_LP_2000[1]=2000.0*V_pure_LP_2000[1]/(83.14467*TK)
|
1439
|
+
|
1440
|
+
# Low pressure
|
1441
|
+
lnphi0_LP=pure_lnphi(0, Z_pure_LP_2000, B_LP, Vc_LP, V_pure_LP_2000, C_LP, D_LP, E_LP, F_LP, g_LP, b_LP)
|
1442
|
+
lnphi1_LP=pure_lnphi(1, Z_pure_LP_2000, B_LP, Vc_LP, V_pure_LP_2000, C_LP, D_LP, E_LP, F_LP, g_LP, b_LP)
|
1443
|
+
|
1444
|
+
|
1445
|
+
|
1446
|
+
# Same with high P
|
1447
|
+
k1_temperature_HP, k2_temperature_HP, k3_temperature_HP, a1_HP, a2_HP, g_HP, b_HP, Vc_HP, B_HP, C_HP, D_HP, E_HP, F_HP, Vguess=get_EOS_params(3000, TK)
|
1448
|
+
Z_pure_HP_2000 = [0.0, 0.0]
|
1449
|
+
V_pure_HP_2000 = [0.0, 0.0]
|
1450
|
+
V_pure_HP_2000[0]=purevolume(0, 100, 2000, B_HP, C_HP, D_HP, E_HP, F_HP, Vc_HP, TK, b_HP, g_HP)
|
1451
|
+
V_pure_HP_2000[1]=purevolume(1, 100, 2000, B_HP, C_HP, D_HP, E_HP, F_HP, Vc_HP, TK, b_HP, g_HP)
|
1452
|
+
Z_pure_HP_2000[0]=2000.0*V_pure_HP_2000[0]/(83.14467*TK)
|
1453
|
+
Z_pure_HP_2000[1]=2000.0*V_pure_HP_2000[1]/(83.14467*TK)
|
1454
|
+
|
1455
|
+
|
1456
|
+
#pure_HP
|
1457
|
+
lnphi0_HP=pure_lnphi(0, Z_pure_HP_2000, B_HP, Vc_HP, V_pure_HP_2000, C_HP, D_HP, E_HP, F_HP, g_HP, b_HP)
|
1458
|
+
lnphi1_HP=pure_lnphi(1, Z_pure_HP_2000, B_HP, Vc_HP, V_pure_HP_2000, C_HP, D_HP, E_HP, F_HP, g_HP, b_HP)
|
1459
|
+
|
1460
|
+
if P>2000:
|
1461
|
+
# This is a weird thing described on Page 6 of Yoshimura -
|
1462
|
+
lnphi0=lnphi0-lnphi0_HP+lnphi0_LP
|
1463
|
+
lnphi1=lnphi1-lnphi1_HP+lnphi1_LP
|
1464
|
+
|
1465
|
+
phi0_pure=math.exp(lnphi0)
|
1466
|
+
phi1_pure=math.exp(lnphi1)
|
1467
|
+
|
1468
|
+
|
1469
|
+
|
1470
|
+
# Now we need to do the mixed fugacity part of this
|
1471
|
+
#--------------------------------------------------------------------------
|
1472
|
+
k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
|
1473
|
+
Bij, Vcij, BVc_prm, BVc, Cijk, Vcijk, CVc2_prm, CVc2, Dijklm, Vcijklm, DVc4_prm, DVc4, Eijklmn, Vcijklmn, EVc5_prm, EVc5, Fij, FVc2_prm, FVc2, bmix, b_prm, gijk, gVc2_prm, gVc2=mixing_rules(B, C, D, E, F, Vc, Y, b, g, k1_temperature, k2_temperature, k3_temperature)
|
1474
|
+
Zmix=(P*Vmix)/(83.14467*TK)
|
1475
|
+
lnphi_mix = [0.0, 0.0]
|
1476
|
+
phi_mix = [0.0, 0.0]
|
1477
|
+
lnphi_mix[0]=mix_lnphi(0, Zmix, BVc_prm, CVc2_prm, DVc4_prm, EVc5_prm, FVc2_prm,FVc2, bmix, b_prm, gVc2, gVc2_prm, Vmix)
|
1478
|
+
lnphi_mix[1]=mix_lnphi(1, Zmix, BVc_prm, CVc2_prm, DVc4_prm, EVc5_prm, FVc2_prm,FVc2, bmix, b_prm, gVc2, gVc2_prm, Vmix)
|
1479
|
+
|
1480
|
+
|
1481
|
+
|
1482
|
+
# But what if P>2000, well we need to do these calcs at low and high P
|
1483
|
+
# High P - using Parameters from up above
|
1484
|
+
Bij_HP, Vcij_HP, BVc_prm_HP, BVc_HP, Cijk_HP, Vcijk_HP, CVc2_prm_HP, CVc2_HP, Dijklm_HP, Vcijklm_HP, DVc4_prm_HP, DVc4_HP, Eijklmn_HP, Vcijklmn_HP, EVc5_prm_HP, EVc5_HP, Fij_HP, FVc2_prm_HP, FVc2_HP, bmix_HP, b_prm_HP, gijk_HP, gVc2_prm_HP, gVc2_HP=mixing_rules(B_HP, C_HP, D_HP, E_HP, F_HP, Vc_HP, Y, b_HP, g_HP, k1_temperature_HP, k2_temperature_HP, k3_temperature_HP)
|
1485
|
+
Vmix_HP=mixvolume(100, 2000, BVc_HP, CVc2_HP, DVc4_HP, EVc5_HP, FVc2_HP, bmix_HP, gVc2_HP, TK)
|
1486
|
+
Zmix_HP=(2000*Vmix_HP)/(83.14467*TK)
|
1487
|
+
lnphi_mix_HP = [0.0, 0.0]
|
1488
|
+
lnphi_mix_HP[0]=mix_lnphi(0, Zmix_HP, BVc_prm_HP, CVc2_prm_HP, DVc4_prm_HP, EVc5_prm_HP, FVc2_prm_HP,FVc2_HP, bmix_HP, b_prm_HP, gVc2_HP, gVc2_prm_HP, Vmix_HP)
|
1489
|
+
lnphi_mix_HP[1]=mix_lnphi(1, Zmix_HP, BVc_prm_HP, CVc2_prm_HP, DVc4_prm_HP, EVc5_prm_HP, FVc2_prm_HP, FVc2_HP, bmix_HP, b_prm_HP, gVc2_HP, gVc2_prm_HP, Vmix_HP)
|
1490
|
+
|
1491
|
+
|
1492
|
+
# Same for LP
|
1493
|
+
Bij_LP, Vcij_LP, BVc_prm_LP, BVc_LP, Cijk_LP, Vcijk_LP, CVc2_prm_LP, CVc2_LP, Dijklm_LP, Vcijklm_LP, DVc4_prm_LP, DVc4_LP, Eijklmn_LP, Vcijklmn_LP, EVc5_prm_LP, EVc5_LP, Fij_LP, FVc2_prm_LP, FVc2_LP, bmix_LP, b_prm_LP, gijk_LP, gVc2_prm_LP, gVc2_LP=mixing_rules(B_LP, C_LP, D_LP, E_LP, F_LP,Vc_LP, Y, b_LP, g_LP, k1_temperature_LP, k2_temperature_LP, k3_temperature_LP)
|
1494
|
+
Vmix_LP=mixvolume(100, 2000, BVc_LP, CVc2_LP, DVc4_LP, EVc5_LP, FVc2_LP, bmix_LP, gVc2_LP, TK)
|
1495
|
+
Zmix_LP=(2000*Vmix_LP)/(83.14467*TK)
|
1496
|
+
lnphi_mix_LP = [0.0, 0.0]
|
1497
|
+
lnphi_mix_LP[0]=mix_lnphi(0, Zmix_LP, BVc_prm_LP, CVc2_prm_LP, DVc4_prm_LP, EVc5_prm_LP, FVc2_prm_LP, FVc2_LP, bmix_LP, b_prm_LP, gVc2_LP, gVc2_prm_LP, Vmix_LP)
|
1498
|
+
lnphi_mix_LP[1]=mix_lnphi(1, Zmix_LP, BVc_prm_LP, CVc2_prm_LP, DVc4_prm_LP, EVc5_prm_LP, FVc2_prm_LP,FVc2_LP, bmix_LP, b_prm_LP, gVc2_LP, gVc2_prm_LP, Vmix_LP)
|
1499
|
+
|
1500
|
+
if P>2000:
|
1501
|
+
lnphi_mix[0]=lnphi_mix[0]-lnphi_mix_HP[0]+lnphi_mix_LP[0]
|
1502
|
+
lnphi_mix[1]=lnphi_mix[1]-lnphi_mix_HP[1]+lnphi_mix_LP[1]
|
1503
|
+
|
1504
|
+
|
1505
|
+
phi_mix[0]=math.exp(lnphi_mix[0])
|
1506
|
+
phi_mix[1]=math.exp(lnphi_mix[1])
|
1507
|
+
|
1508
|
+
|
1509
|
+
|
1510
|
+
|
1511
|
+
|
1512
|
+
|
1513
|
+
|
1514
|
+
activity[0] = phi_mix[0] * Y[0] / phi0_pure
|
1515
|
+
activity[1] = phi_mix[1] * Y[1] / phi1_pure
|
1516
|
+
|
1517
|
+
f[0] = Y[0] * P * phi_mix[0] / 1000.0 # fugacity in kbar
|
1518
|
+
f[1] = Y[1] * P * phi_mix[1] / 1000.0 # fugacity in kbar
|
1519
|
+
|
1520
|
+
|
1521
|
+
|
1522
|
+
|
1523
|
+
return f[0], f[1], activity[0], activity[1], Zmix
|
1524
|
+
|
1525
|
+
|
1526
|
+
|
1527
|
+
|
1528
|
+
|
1529
|
+
def mixing_rules(B, C, D, E, F, Vc, Y, b, g, k1_temperature, k2_temperature, k3_temperature):
|
1530
|
+
""" This function applies the DZ06 mixing rules"""
|
1531
|
+
|
1532
|
+
Bij = np.zeros((2, 2))
|
1533
|
+
Vcij = np.zeros((2, 2))
|
1534
|
+
BVc_prm = np.zeros(2)
|
1535
|
+
b_prm=np.zeros(2)
|
1536
|
+
BVc = 0.0
|
1537
|
+
Cijk = np.zeros((2, 2, 2))
|
1538
|
+
Vcijk = np.zeros((2, 2, 2))
|
1539
|
+
CVc2_prm = np.zeros(2)
|
1540
|
+
CVc2 = 0.0
|
1541
|
+
Dijklm = np.zeros((2, 2, 2, 2, 2))
|
1542
|
+
Vcijklm = np.zeros((2, 2, 2, 2, 2))
|
1543
|
+
Eijklmn=np.zeros((2, 2, 2, 2, 2, 2))
|
1544
|
+
Vcijklmn=np.zeros((2, 2, 2, 2, 2, 2))
|
1545
|
+
DVc4_prm=np.zeros(2)
|
1546
|
+
DVc4=0
|
1547
|
+
|
1548
|
+
EVc5_prm = np.zeros(2)
|
1549
|
+
EVc5 = 0.0
|
1550
|
+
Fij = np.zeros((2, 2))
|
1551
|
+
FVc2_prm = np.zeros(2)
|
1552
|
+
FVc2 = 0.0
|
1553
|
+
gijk = np.zeros((2, 2, 2))
|
1554
|
+
gVc2_prm = np.zeros(2)
|
1555
|
+
gVc2 = 0.0
|
1556
|
+
|
1557
|
+
for i in range(2):
|
1558
|
+
for j in range(2):
|
1559
|
+
k1 = 1.0 if i == j else k1_temperature
|
1560
|
+
Bij[i, j] = pow((cbrt_calc(B[i]) + cbrt_calc(B[j]))/2, 3.0) * k1
|
1561
|
+
|
1562
|
+
for i in range(2):
|
1563
|
+
for j in range(2):
|
1564
|
+
Vcij[i, j] = pow((cbrt_calc(Vc[i]) + cbrt_calc(Vc[j]))/2, 3.0)
|
1565
|
+
|
1566
|
+
|
1567
|
+
for i in range(2):
|
1568
|
+
for j in range(2):
|
1569
|
+
BVc_prm[i] += 2 * Y[j] * Bij[i, j] * Vcij[i, j]
|
1570
|
+
|
1571
|
+
|
1572
|
+
|
1573
|
+
for i in range(2):
|
1574
|
+
for j in range(2):
|
1575
|
+
|
1576
|
+
BVc += Y[i] * Y[j] * Bij[i, j] * Vcij[i, j]
|
1577
|
+
|
1578
|
+
for i in range(2):
|
1579
|
+
for j in range(2):
|
1580
|
+
for k in range(2):
|
1581
|
+
k2 = 1.0 if i == j and j == k else k2_temperature
|
1582
|
+
Cijk[i, j, k] = pow((cbrt_calc(C[i]) + cbrt_calc(C[j]) + cbrt_calc(C[k]))/3, 3.0) * k2
|
1583
|
+
|
1584
|
+
for i in range(2):
|
1585
|
+
for j in range(2):
|
1586
|
+
for k in range(2):
|
1587
|
+
Vcijk[i, j, k] = pow((cbrt_calc(Vc[i]) + cbrt_calc(Vc[j]) + cbrt_calc(Vc[k]))/3, 3.0)
|
1588
|
+
|
1589
|
+
for i in range(2):
|
1590
|
+
for j in range(2):
|
1591
|
+
for k in range(2):
|
1592
|
+
CVc2_prm[i] += 3 * Y[j] * Y[k] * Cijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
|
1593
|
+
|
1594
|
+
CVc2=0.0
|
1595
|
+
for i in range(2):
|
1596
|
+
for j in range(2):
|
1597
|
+
for k in range(2):
|
1598
|
+
CVc2 += Y[i] * Y[j] * Y[k] * Cijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
|
1599
|
+
|
1600
|
+
|
1601
|
+
for i in range(2):
|
1602
|
+
for j in range(2):
|
1603
|
+
for k in range(2):
|
1604
|
+
for l in range(2):
|
1605
|
+
for m in range(2):
|
1606
|
+
Dijklm[i, j, k, l, m] = pow((cbrt_calc(D[i]) + cbrt_calc(D[j]) + cbrt_calc(D[k]) + cbrt_calc(D[l]) + cbrt_calc(D[m]))/5, 3.0)
|
1607
|
+
|
1608
|
+
for i in range(2):
|
1609
|
+
for j in range(2):
|
1610
|
+
for k in range(2):
|
1611
|
+
for l in range(2):
|
1612
|
+
for m in range(2):
|
1613
|
+
Vcijklm[i, j, k, l, m] = pow((cbrt_calc(Vc[i]) + cbrt_calc(Vc[j]) + cbrt_calc(Vc[k]) + cbrt_calc(Vc[l]) + cbrt_calc(Vc[m]))/5, 3.0)
|
1614
|
+
|
1615
|
+
|
1616
|
+
for i in range(2):
|
1617
|
+
for j in range(2):
|
1618
|
+
for k in range(2):
|
1619
|
+
for l in range(2):
|
1620
|
+
for m in range(2):
|
1621
|
+
DVc4_prm[i] += 5.0 * Y[j] * Y[k] * Y[l] * Y[m] * Dijklm[i, j, k, l, m] * pow(Vcijklm[i, j, k, l, m], 4.0)
|
1622
|
+
|
1623
|
+
|
1624
|
+
for i in range(2):
|
1625
|
+
for j in range(2):
|
1626
|
+
for k in range(2):
|
1627
|
+
for l in range(2):
|
1628
|
+
for m in range(2):
|
1629
|
+
DVc4 += Y[i] * Y[j] * Y[k] * Y[l] * Y[m] * Dijklm[i, j, k, l, m] * pow(Vcijklm[i, j, k, l, m], 4)
|
1630
|
+
|
1631
|
+
# Missing Eijklmn,
|
1632
|
+
for i in range(2):
|
1633
|
+
for j in range(2):
|
1634
|
+
for k in range(2):
|
1635
|
+
for l in range(2):
|
1636
|
+
for m in range(2):
|
1637
|
+
for n in range(2):
|
1638
|
+
Eijklmn[i, j, k, l, m, n] = pow((cbrt_calc(E[i]) + cbrt_calc(E[j]) + cbrt_calc(E[k]) + cbrt_calc(E[l]) + cbrt_calc(E[m]) + cbrt_calc(E[n]))/6, 3.0)
|
1639
|
+
|
1640
|
+
|
1641
|
+
for i in range(2):
|
1642
|
+
for j in range(2):
|
1643
|
+
for k in range(2):
|
1644
|
+
for l in range(2):
|
1645
|
+
for m in range(2):
|
1646
|
+
for n in range(2):
|
1647
|
+
Vcijklmn[i, j, k, l, m, n] = pow((cbrt_calc(Vc[i]) + cbrt_calc(Vc[j]) + cbrt_calc(Vc[k]) + cbrt_calc(Vc[l]) + cbrt_calc(Vc[m]) + cbrt_calc(Vc[n]))/6, 3.0)
|
1648
|
+
|
1649
|
+
|
1650
|
+
|
1651
|
+
for i in range(2):
|
1652
|
+
for j in range(2):
|
1653
|
+
for k in range(2):
|
1654
|
+
for l in range(2):
|
1655
|
+
for m in range(2):
|
1656
|
+
for n in range(2):
|
1657
|
+
EVc5_prm[i] += 6.0 * Y[j] * Y[k] * Y[l] * Y[m] * Y[n] * Eijklmn[i, j, k, l, m, n] * pow(Vcijklmn[i, j, k, l, m, n], 5.0)
|
1658
|
+
|
1659
|
+
|
1660
|
+
for i in range(2):
|
1661
|
+
for j in range(2):
|
1662
|
+
for k in range(2):
|
1663
|
+
for l in range(2):
|
1664
|
+
for m in range(2):
|
1665
|
+
for n in range(2):
|
1666
|
+
EVc5 += Y[i] * Y[j] * Y[k] * Y[l] * Y[m] * Y[n] * Eijklmn[i, j, k, l, m, n] * pow(Vcijklmn[i, j, k, l, m, n], 5.0)
|
1667
|
+
|
1668
|
+
for i in range(2):
|
1669
|
+
for j in range(2):
|
1670
|
+
Fij[i, j] = pow((cbrt_calc(F[i]) + cbrt_calc(F[j]))/2, 3.0)
|
1671
|
+
|
1672
|
+
for i in range(2):
|
1673
|
+
for j in range(2):
|
1674
|
+
FVc2_prm[i] += 2.0 * Y[j] * Fij[i, j] * Vcij[i, j] * Vcij[i, j]
|
1675
|
+
|
1676
|
+
for i in range(2):
|
1677
|
+
for j in range(2):
|
1678
|
+
FVc2 += Y[i] * Y[j] * Fij[i, j] * Vcij[i, j] * Vcij[i, j]
|
1679
|
+
|
1680
|
+
bmix = Y[0] * b[0] + Y[1] * b[1]
|
1681
|
+
|
1682
|
+
b_prm[0] = b[0]
|
1683
|
+
b_prm[1] = b[1]
|
1684
|
+
|
1685
|
+
for i in range(2):
|
1686
|
+
for j in range(2):
|
1687
|
+
for k in range(2):
|
1688
|
+
k3 = 1.0 if i == j and j == k else k3_temperature
|
1689
|
+
gijk[i, j, k] = pow((cbrt_calc(g[i]) + cbrt_calc(g[j]) + cbrt_calc(g[k]))/3, 3.0) * k3
|
1690
|
+
|
1691
|
+
for i in range(2):
|
1692
|
+
for j in range(2):
|
1693
|
+
for k in range(2):
|
1694
|
+
gVc2_prm[i] += 3.0 * Y[j] * Y[k] * gijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
|
1695
|
+
|
1696
|
+
|
1697
|
+
for i in range(2):
|
1698
|
+
for j in range(2):
|
1699
|
+
for k in range(2):
|
1700
|
+
gVc2 += Y[i] * Y[j] * Y[k] * gijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
|
1701
|
+
|
1702
|
+
|
1703
|
+
return Bij, Vcij, BVc_prm, BVc, Cijk, Vcijk, CVc2_prm, CVc2, Dijklm, Vcijklm, DVc4_prm, DVc4, Eijklmn, Vcijklmn, EVc5_prm, EVc5, Fij, FVc2_prm, FVc2, bmix, b_prm, gijk, gVc2_prm, gVc2
|
1704
|
+
|
1705
|
+
|
1706
|
+
## Getting EOS contsants themselves
|
1707
|
+
|
1708
|
+
def get_EOS_params(P, TK):
|
1709
|
+
""" This function returns the EOS 'constants' if you know the pressure (in bars) and temperature (in Kelvin)
|
1710
|
+
|
1711
|
+
"""
|
1712
|
+
|
1713
|
+
a1 = np.zeros(16)
|
1714
|
+
a2 = np.zeros(16)
|
1715
|
+
b = np.zeros(2)
|
1716
|
+
g = np.zeros(2)
|
1717
|
+
Vc = np.zeros(1)
|
1718
|
+
B = np.zeros(2)
|
1719
|
+
C = np.zeros(2)
|
1720
|
+
D = np.zeros(2)
|
1721
|
+
E = np.zeros(2)
|
1722
|
+
F = np.zeros(2)
|
1723
|
+
V = np.zeros(2)
|
1724
|
+
Vc = np.zeros(2)
|
1725
|
+
|
1726
|
+
|
1727
|
+
|
1728
|
+
|
1729
|
+
|
1730
|
+
|
1731
|
+
|
1732
|
+
|
1733
|
+
# Initial guess for volumne
|
1734
|
+
|
1735
|
+
if P<=2000:
|
1736
|
+
Vguess=1000
|
1737
|
+
elif P>20000:
|
1738
|
+
Vguess=10
|
1739
|
+
else:
|
1740
|
+
Vguess=100
|
1741
|
+
|
1742
|
+
|
1743
|
+
if P <= 2000.0:
|
1744
|
+
for i in range(16):
|
1745
|
+
a1[i] = aL1[i]
|
1746
|
+
a2[i] = aL2[i]
|
1747
|
+
# These are the binary interaction parameters
|
1748
|
+
k1_temperature = 3.131 - (5.0624 / 10**3.0) * TK + (1.8641 / 10**6) * TK**2 - 31.409 / TK
|
1749
|
+
k2_temperature = -46.646 + (4.2877 / 10**2.0) * TK - (1.0892 / 10**5) * TK**2 + 1.5782 * 10**4 / TK
|
1750
|
+
k3_temperature = 0.9
|
1751
|
+
else:
|
1752
|
+
for i in range(16):
|
1753
|
+
a1[i] = aH1[i]
|
1754
|
+
a2[i] = aH2[i]
|
1755
|
+
# Same, but for higher pressures
|
1756
|
+
k1_temperature = 9.034 - (7.9212 / 10**3) * TK + (2.3285 / 10**6) * TK**2 - 2.4221 * 10**3 / TK
|
1757
|
+
k2_temperature = -1.068 + (1.8756 / 10**3) * TK - (4.9371 / 10**7) * TK**2 + 6.6180 * 10**2 / TK
|
1758
|
+
k3_temperature = 1.0
|
1759
|
+
|
1760
|
+
b[0] = a1[14] # beta for H2O
|
1761
|
+
b[1] = a2[14] # beta for CO2
|
1762
|
+
g[0] = a1[15] # gamma for H2O
|
1763
|
+
g[1] = a2[15] # gamma for CO2
|
1764
|
+
|
1765
|
+
Vc[0] = 83.14467 * Tc1 / Pc1
|
1766
|
+
B[0] = a1[1] + a1[2] / ((TK / Tc1) * (TK / Tc1)) + a1[3] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
|
1767
|
+
C[0] = a1[4] + a1[5] / ((TK / Tc1) * (TK / Tc1)) + a1[6] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
|
1768
|
+
D[0] = a1[7] + a1[8] / ((TK / Tc1) * (TK / Tc1)) + a1[9] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
|
1769
|
+
E[0] = a1[10] + a1[11] / ((TK / Tc1) * (TK / Tc1)) + a1[12] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
|
1770
|
+
F[0] = a1[13] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
|
1771
|
+
|
1772
|
+
Vc[1] = 83.14467 * Tc2 / Pc2
|
1773
|
+
B[1] = a2[1] + a2[2] / ((TK / Tc2) * (TK / Tc2)) + a2[3] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
|
1774
|
+
C[1] = a2[4] + a2[5] / ((TK / Tc2) * (TK / Tc2)) + a2[6] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
|
1775
|
+
D[1] = a2[7] + a2[8] / ((TK / Tc2) * (TK / Tc2)) + a2[9] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
|
1776
|
+
E[1] = a2[10] + a2[11] / ((TK / Tc2) * (TK / Tc2)) + a2[12] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
|
1777
|
+
F[1] = a2[13] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
|
1778
|
+
return k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess
|
1779
|
+
|
1780
|
+
## Lets wrap all these functions up.
|
1781
|
+
|
1782
|
+
def calculate_molar_volume_ind_DZ2006(*, P_kbar, T_K, XH2O):
|
1783
|
+
""" This function calculates molar volume (cm3/mol) for a known pressure (kbar), T in K and XH2O (mol frac) for a single value
|
1784
|
+
"""
|
1785
|
+
|
1786
|
+
P=P_kbar*1000
|
1787
|
+
TK=T_K
|
1788
|
+
|
1789
|
+
# Calculate the constants you neeed
|
1790
|
+
k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
|
1791
|
+
|
1792
|
+
if XH2O==0:
|
1793
|
+
mol_vol=purevolume(1, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
|
1794
|
+
|
1795
|
+
if XH2O==1:
|
1796
|
+
mol_vol=purevolume(0, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
|
1797
|
+
|
1798
|
+
else:
|
1799
|
+
XCO2=1-XH2O
|
1800
|
+
Y = [0] * 2
|
1801
|
+
Y[0]=XH2O
|
1802
|
+
Y[1]=XCO2
|
1803
|
+
Bij, Vcij, BVc_prm, BVc, Cijk, Vcijk, CVc2_prm, CVc2, Dijklm, Vcijklm, DVc4_prm, DVc4, Eijklmn, Vcijklmn, EVc5_prm, EVc5, Fij, FVc2_prm, FVc2, bmix, b_prm, gijk, gVc2_prm, gVc2=mixing_rules(B, C,D, E, F, Vc, Y, b, g, k1_temperature, k2_temperature, k3_temperature)
|
1804
|
+
|
1805
|
+
|
1806
|
+
mol_vol=mixvolume(Vguess, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, T_K)
|
1807
|
+
|
1808
|
+
if mol_vol<0:
|
1809
|
+
mol_vol=np.nan
|
1810
|
+
|
1811
|
+
return mol_vol
|
1812
|
+
|
1813
|
+
|
1814
|
+
def calculate_molar_volume_DZ2006(*, P_kbar, T_K, XH2O):
|
1815
|
+
""" Used to calculate molar volume (cm3/mol) in a loop for multiple inputs
|
1816
|
+
|
1817
|
+
|
1818
|
+
"""
|
1819
|
+
|
1820
|
+
P_kbar, T_K, XH2O=ensure_series(P_kbar, T_K, XH2O)
|
1821
|
+
|
1822
|
+
# Check all the same length
|
1823
|
+
lengths = [len(P_kbar), len(T_K), len(XH2O)]
|
1824
|
+
if len(set(lengths)) != 1:
|
1825
|
+
raise ValueError("All input Pandas Series must have the same length.")
|
1826
|
+
|
1827
|
+
# Set up loop
|
1828
|
+
mol_vol=np.zeros(len(P_kbar), float)
|
1829
|
+
|
1830
|
+
for i in range(0, len(P_kbar)):
|
1831
|
+
mol_vol[i]=calculate_molar_volume_ind_DZ2006(P_kbar=P_kbar.iloc[i].astype(float), T_K=T_K.iloc[i].astype(float), XH2O=XH2O.iloc[i].astype(float))
|
1832
|
+
|
1833
|
+
|
1834
|
+
|
1835
|
+
|
1836
|
+
|
1837
|
+
return mol_vol
|
1838
|
+
|
1839
|
+
def calculate_Pressure_ind_DZ2006(*, mol_vol, T_K, XH2O, Pguess=None):
|
1840
|
+
""" This function calculates pressure (in bars) for a known molar volume, T in K and XH2O (mol frac) for a single value. It uses a look up table to get pressure, then a newton and raphson method (implemented in the function mixpressure) to find the best fit pressure. There are some densities, T_K and XH2O values where the volume is negative.
|
1841
|
+
"""
|
1842
|
+
V=mol_vol
|
1843
|
+
# if Pguess is None:
|
1844
|
+
# if V>1000:
|
1845
|
+
# Pguess=1000
|
1846
|
+
# elif V<10:
|
1847
|
+
# Pguess=20000
|
1848
|
+
# else:
|
1849
|
+
# Pguess=200
|
1850
|
+
|
1851
|
+
# Lets get P guess from a look up table
|
1852
|
+
# uses a look up table
|
1853
|
+
Pguess=get_initial_guess(V_target=V, T_K_target=T_K, XH2O_target=XH2O)*1000
|
1854
|
+
|
1855
|
+
if Pguess <= 0:
|
1856
|
+
return np.nan
|
1857
|
+
|
1858
|
+
|
1859
|
+
TK=T_K
|
1860
|
+
|
1861
|
+
# lets do for low pressure initially
|
1862
|
+
|
1863
|
+
|
1864
|
+
# if XH2O==0:
|
1865
|
+
# P=purepressure(1, V, Pguess, TK)
|
1866
|
+
#
|
1867
|
+
# elif XH2O==1:
|
1868
|
+
# P=purepressure(0, V, Pguess, TK)
|
1869
|
+
#
|
1870
|
+
# else:
|
1871
|
+
XCO2=1-XH2O
|
1872
|
+
Y = [0] * 2
|
1873
|
+
Y[0]=XH2O
|
1874
|
+
Y[1]=XCO2
|
1875
|
+
|
1876
|
+
# Now iteratively solve for pressure starting from this initial guess.
|
1877
|
+
|
1878
|
+
P=mixpressure(Pguess, V, T_K, Y)
|
1879
|
+
|
1880
|
+
return P
|
1881
|
+
|
1882
|
+
def calculate_Pressure_DZ2006(*, mol_vol=None, density=None, T_K, XH2O):
|
1883
|
+
""" Used to calculate pressure in a loop for multiple inputs.
|
1884
|
+
Den - bulk density.
|
1885
|
+
|
1886
|
+
Parameters
|
1887
|
+
----------------
|
1888
|
+
mol_vol: molar volume in g/cm3
|
1889
|
+
density: density in g/cm3
|
1890
|
+
T_K: entrapment temperature in kelvin
|
1891
|
+
XH2O: molar fraction of H2O in the fluid
|
1892
|
+
|
1893
|
+
Returns
|
1894
|
+
----------------
|
1895
|
+
Pressure in bars
|
1896
|
+
|
1897
|
+
|
1898
|
+
"""
|
1899
|
+
# Make all a panda series
|
1900
|
+
|
1901
|
+
|
1902
|
+
|
1903
|
+
if mol_vol is None and density is not None:
|
1904
|
+
mol_vol=density_to_mol_vol(density=density, XH2O=XH2O)
|
1905
|
+
|
1906
|
+
mol_vol, T_K, XH2O=ensure_series(mol_vol, T_K, XH2O)
|
1907
|
+
|
1908
|
+
# Check all the same length
|
1909
|
+
lengths = [len(mol_vol), len(T_K), len(XH2O)]
|
1910
|
+
if len(set(lengths)) != 1:
|
1911
|
+
raise ValueError("All input Pandas Series must have the same length.")
|
1912
|
+
|
1913
|
+
# Set up loop
|
1914
|
+
P=np.zeros(len(mol_vol), float)
|
1915
|
+
|
1916
|
+
for i in range(0, len(mol_vol)):
|
1917
|
+
P[i]=calculate_Pressure_ind_DZ2006(mol_vol=mol_vol.iloc[i].astype(float), T_K=T_K.iloc[i].astype(float), XH2O=XH2O.iloc[i].astype(float))
|
1918
|
+
|
1919
|
+
|
1920
|
+
|
1921
|
+
return P
|
1922
|
+
|
1923
|
+
|
1924
|
+
def mix_fugacity(*, P_kbar, T_K, XH2O, Vmix):
|
1925
|
+
|
1926
|
+
""" Used to calculate fugacity, compressability and activities for a panda series
|
1927
|
+
|
1928
|
+
"""
|
1929
|
+
# Make everything a pandas series
|
1930
|
+
|
1931
|
+
P_kbar, T_K, XH2O, Vmix=ensure_series_4(P_kbar, T_K, XH2O, Vmix)
|
1932
|
+
|
1933
|
+
|
1934
|
+
|
1935
|
+
#Check all the same length
|
1936
|
+
lengths = [len(P_kbar), len(T_K), len(XH2O), len(Vmix)]
|
1937
|
+
if len(set(lengths)) != 1:
|
1938
|
+
raise ValueError("All input Pandas Series must have the same length.")
|
1939
|
+
|
1940
|
+
f=np.zeros(len(P_kbar), float)
|
1941
|
+
a_CO2=np.zeros(len(P_kbar), float)
|
1942
|
+
a_H2O=np.zeros(len(P_kbar), float)
|
1943
|
+
f_CO2=np.zeros(len(P_kbar), float)
|
1944
|
+
f_H2O=np.zeros(len(P_kbar), float)
|
1945
|
+
Zmix=np.zeros(len(P_kbar), float)
|
1946
|
+
for i in range(0, len(P_kbar)):
|
1947
|
+
|
1948
|
+
f_H2O[i], f_CO2[i], a_H2O[i], a_CO2[i], Zmix[i]=mix_fugacity_ind(P_kbar=P_kbar.iloc[i].astype(float), T_K=T_K.iloc[i].astype(float), XH2O=XH2O.iloc[i].astype(float), Vmix=Vmix.iloc[i].astype(float))
|
1949
|
+
|
1950
|
+
return f_H2O, f_CO2, a_H2O,a_CO2, Zmix
|
1951
|
+
|
1952
|
+
|
1953
|
+
def mol_vol_to_density(*, mol_vol, XH2O):
|
1954
|
+
""" Converts molar mass g/mol to densit g/cm3 for a given XH2O"""
|
1955
|
+
density=((1-XH2O)*44 + (XH2O)*18)/mol_vol
|
1956
|
+
return density
|
1957
|
+
|
1958
|
+
def density_to_mol_vol(*, density, XH2O):
|
1959
|
+
""" Converts density in g/cm3 to molar volume (mol/cm3) for a given XH2O"""
|
1960
|
+
mol_vol=((1-XH2O)*44 + (XH2O)*18)/density
|
1961
|
+
return mol_vol
|
1962
|
+
|
1963
|
+
|
1964
|
+
|
1965
|
+
def calc_prop_knownP_EOS_DZ2006(*, P_kbar=1, T_K=1200, XH2O=1):
|
1966
|
+
""" This function calculates molar volume, density, compressability factor, fugacity, and activity for mixed H2O-CO2 fluids
|
1967
|
+
using the EOS of Span and Wanger. It assumes you know P, T, and XH2O.
|
1968
|
+
|
1969
|
+
Parameters
|
1970
|
+
-------------------
|
1971
|
+
P_kbar: float, np.array, pd.Series
|
1972
|
+
Pressure in kbar
|
1973
|
+
T_K: float, np.array, pd.Series
|
1974
|
+
Temperature in Kelvin
|
1975
|
+
XH2O: float, np.array, pd.Series
|
1976
|
+
Molar fraction of H2O in the fluid phase.
|
1977
|
+
|
1978
|
+
Returns
|
1979
|
+
-------------------
|
1980
|
+
pd.DataFrame
|
1981
|
+
|
1982
|
+
"""
|
1983
|
+
|
1984
|
+
|
1985
|
+
|
1986
|
+
# First, check all pd Series
|
1987
|
+
|
1988
|
+
|
1989
|
+
mol_vol=calculate_molar_volume_DZ2006(P_kbar=P_kbar, T_K=T_K, XH2O=XH2O)
|
1990
|
+
|
1991
|
+
|
1992
|
+
f_H2O, f_CO2, a_H2O, a_CO2, Zmix=mix_fugacity(P_kbar=P_kbar, T_K=T_K, XH2O=XH2O,
|
1993
|
+
Vmix=mol_vol)
|
1994
|
+
density=mol_vol_to_density(mol_vol=mol_vol, XH2O=XH2O)
|
1995
|
+
# 'T_K': T_K,
|
1996
|
+
# 'P_kbar': P_kbar,
|
1997
|
+
# 'XH2O': XH2O,
|
1998
|
+
#
|
1999
|
+
|
2000
|
+
|
2001
|
+
df=pd.DataFrame(data={'P_kbar': P_kbar,
|
2002
|
+
'T_K': T_K,
|
2003
|
+
'XH2O': XH2O,
|
2004
|
+
'XCO2': 1-XH2O,
|
2005
|
+
'Molar Volume (cm3/mol)': mol_vol,
|
2006
|
+
'Density (g/cm3)': density,
|
2007
|
+
'Compressability_factor': Zmix,
|
2008
|
+
'fugacity_H2O (kbar)': f_H2O,
|
2009
|
+
'fugacity_CO2 (kbar)': f_CO2,
|
2010
|
+
'activity_H2O': a_H2O,
|
2011
|
+
'activity_CO2': a_CO2})
|
2012
|
+
|
2013
|
+
return df
|
2014
|
+
|
2015
|
+
|
2016
|
+
|
2017
|
+
def calculate_entrapment_P_XH2O(*, XH2O, CO2_dens_gcm3, T_K, T_K_ambient=37+273.15, fast_calcs=False, Hloss=True):
|
2018
|
+
"""" This function calculates pressure for a measured CO$_2$ density, temperature and estimate of initial XH2O.
|
2019
|
+
It first corrects the density to obtain a bulk density for a CO2-H2O mix, assuming that H2O was lost from the inclusion.
|
2020
|
+
correcting for XH2O. It assumes that H2O has been lost from the inclusion (see Hansteen and Klugel, 2008 for method). It also calculates using other
|
2021
|
+
pure CO2 equation of states for comparison
|
2022
|
+
|
2023
|
+
Parameters
|
2024
|
+
----------------------
|
2025
|
+
XH2O: float, pd.Series.
|
2026
|
+
The molar fraction of H2O in the fluid. Should be between 0 and 1. Can get an estimate from say VESical.
|
2027
|
+
|
2028
|
+
CO2_dens_gcm3: float, pd.Series
|
2029
|
+
Measured CO2 density in g/cm3
|
2030
|
+
|
2031
|
+
T_K: float, pd.Series
|
2032
|
+
Temperature in Kelvin fluid was trapped at
|
2033
|
+
|
2034
|
+
T_K_ambient: pd.Series
|
2035
|
+
Temperature in Kelvin Raman measurement was made at.
|
2036
|
+
|
2037
|
+
fast_calcs: bool (default False)
|
2038
|
+
If True, only performs one EOS calc for DZ06, not 4 (with water, without water, SP94 and SW96).
|
2039
|
+
also specify H2Oloss=True or False
|
2040
|
+
|
2041
|
+
|
2042
|
+
|
2043
|
+
Returns
|
2044
|
+
-----------------------------
|
2045
|
+
if fast_calcs is False:
|
2046
|
+
pd.DataFrame:
|
2047
|
+
Columns showing:
|
2048
|
+
P_kbar_pureCO2_SW96: Pressure calculated for the measured CO$_2$ density using the pure CO2 EOS from Span and Wanger (1996)
|
2049
|
+
P_kbar_pureCO2_SP94: Pressure calculated for the measured CO$_2$ density using the pure CO2 EOS from Sterner and Pitzer (1994)
|
2050
|
+
P_kbar_pureCO2_DZ06: Pressure calculated from the measured CO$_2$ density using the pure CO2 EOs from Duan and Zhang (2006)
|
2051
|
+
P_kbar_mixCO2_DZ06_Hloss: Pressure calculated from the reconstructed mixed fluid density using the mixed EOS from Duan and Zhang (2006) assuming H loss
|
2052
|
+
P_kbar_mixCO2_DZ06_noHloss: Pressure calculated from the reconstructed mixed fluid density using the mixed EOS from Duan and Zhang (2006) assuming H loss
|
2053
|
+
P Mix_Hloss/P Pure DZ06: Correction factor - e.g. how much deeper the pressure is from the mixed EOS with H loss
|
2054
|
+
P Mix_noHloss/P Pure DZ06: Correction factor - e.g. how much deeper the pressure is from the mixed EOS (assuming no H loss)
|
2055
|
+
rho_mix_calc_noHloss: Bulk density calculated (C+H)
|
2056
|
+
rho_mix_calc_Hloss: Bulk density calculated (C+H) after h loss
|
2057
|
+
CO2_dens_gcm3: Input CO2 density
|
2058
|
+
T_K: input temperature
|
2059
|
+
XH2O: input molar fraction of H2O
|
2060
|
+
|
2061
|
+
if fast_calcs is True:
|
2062
|
+
P_kbar_mixCO2_DZ06: Pressure calculated from the reconstructed mixed fluid density using the mixed EOS from Duan and Zhang (2006)
|
2063
|
+
|
2064
|
+
|
2065
|
+
|
2066
|
+
"""
|
2067
|
+
XH2O, rho_meas, T_K=ensure_series(a=XH2O, b=CO2_dens_gcm3, c=T_K)
|
2068
|
+
alpha=XH2O/(1-XH2O)
|
2069
|
+
|
2070
|
+
# IF water is lost
|
2071
|
+
rho_orig_H_loss=rho_meas*(1+alpha*(18/44))
|
2072
|
+
# IF water isnt lost
|
2073
|
+
|
2074
|
+
# Calculate mass ratio from molar ratio
|
2075
|
+
XH2O_mass=(XH2O*18)/((1-XH2O)*44 +(XH2O*18) )
|
2076
|
+
# Calculate pressure in CO2 fluid - returns answer in kbar
|
2077
|
+
P_CO2=calculate_P_for_rho_T_SW96(CO2_dens_gcm3, T_K_ambient)['P_kbar']
|
2078
|
+
# Now calculate density of H2O fluid
|
2079
|
+
|
2080
|
+
# See https://www.youtube.com/watch?v=6wE4Tk6OjGY
|
2081
|
+
Ptotal=P_CO2/(1-XH2O) # Calculate the total pressure from the pressure we know for CO2.
|
2082
|
+
P_H2O=Ptotal*XH2O # Now calculate the pressure of H2O. You could also do this as PTot*XH2O
|
2083
|
+
|
2084
|
+
# calculate density of H2O using EOS
|
2085
|
+
H2O_dens=calculate_rho_for_P_T_H2O(P_kbar=P_H2O,T_K=T_K_ambient)
|
2086
|
+
|
2087
|
+
# Calculate the bulk density by re-arranging the two volume equations
|
2088
|
+
nan_mask = H2O_dens==0
|
2089
|
+
rho_orig_no_H_loss=(CO2_dens_gcm3*H2O_dens)/((1-XH2O_mass)*H2O_dens+XH2O_mass*CO2_dens_gcm3)
|
2090
|
+
rho_orig_no_H_loss = np.where(nan_mask, CO2_dens_gcm3, rho_orig_no_H_loss)
|
2091
|
+
|
2092
|
+
|
2093
|
+
|
2094
|
+
if fast_calcs is True:
|
2095
|
+
if Hloss is True:
|
2096
|
+
P=calculate_Pressure_DZ2006(density=rho_orig_H_loss, T_K=T_K, XH2O=XH2O)
|
2097
|
+
if Hloss is False:
|
2098
|
+
P=calculate_Pressure_DZ2006(density=rho_orig_no_H_loss, T_K=T_K, XH2O=XH2O)
|
2099
|
+
return P/1000
|
2100
|
+
|
2101
|
+
else:
|
2102
|
+
|
2103
|
+
# Lets calculate the pressure using SW96
|
2104
|
+
P_SW=calculate_P_for_rho_T(T_K=T_K, CO2_dens_gcm3=rho_meas, EOS='SW96')
|
2105
|
+
P_SP=calculate_P_for_rho_T(T_K=T_K, CO2_dens_gcm3=rho_meas, EOS='SP94')
|
2106
|
+
# Run Duan and Zhang with no XHO@ to start with
|
2107
|
+
P_DZ=calculate_Pressure_DZ2006(density=rho_meas, T_K=T_K, XH2O=XH2O*0)
|
2108
|
+
# Now doing it with XH2O for two different densities
|
2109
|
+
|
2110
|
+
P_DZ_mix_H_loss=calculate_Pressure_DZ2006(density=rho_orig_H_loss, T_K=T_K, XH2O=XH2O)
|
2111
|
+
P_DZ_mix_noH_loss=calculate_Pressure_DZ2006(density=rho_orig_no_H_loss, T_K=T_K, XH2O=XH2O)
|
2112
|
+
|
2113
|
+
df=pd.DataFrame(data={
|
2114
|
+
'P_kbar_pureCO2_SW96': P_SW['P_kbar'],
|
2115
|
+
'P_kbar_pureCO2_SP94': P_SP['P_kbar'],
|
2116
|
+
'P_kbar_pureCO2_DZ06': P_DZ/1000,
|
2117
|
+
'P_kbar_mixCO2_DZ06_Hloss': P_DZ_mix_H_loss/1000,
|
2118
|
+
'P_kbar_mixCO2_DZ06_no_Hloss': P_DZ_mix_noH_loss/1000,
|
2119
|
+
'P Mix_Hloss/P Pure DZ06': P_DZ_mix_H_loss/P_DZ,
|
2120
|
+
'P Mix_no_Hloss/P Pure DZ06': P_DZ_mix_noH_loss/P_DZ,
|
2121
|
+
'rho_mix_calc_Hloss': rho_orig_H_loss,
|
2122
|
+
'rho_mix_calc_noHloss': rho_orig_no_H_loss,
|
2123
|
+
'CO2_dens_gcm3': rho_meas,
|
2124
|
+
'T_K': T_K,
|
2125
|
+
'XH2O': XH2O})
|
2126
|
+
|
2127
|
+
return df
|
2128
|
+
|
2129
|
+
|
2130
|
+
def calculate_rho_for_P_T_H2O(P_kbar, T_K):
|
2131
|
+
""" This function calculates H2O density in g/cm3 for a known Pressure (in kbar), a known T (in K) using the Wanger and Pru (2002) EOS from CoolProp
|
2132
|
+
doi:10.1063/1.1461829.
|
2133
|
+
|
2134
|
+
Parameters
|
2135
|
+
---------------------
|
2136
|
+
P_kbar: int, float, pd.Series, np.array
|
2137
|
+
Pressure in kbar
|
2138
|
+
|
2139
|
+
T_K: int, float, pd.Series, np.array
|
2140
|
+
Temperature in Kelvin
|
2141
|
+
|
2142
|
+
Returns
|
2143
|
+
--------------------
|
2144
|
+
pd.Series
|
2145
|
+
H2O density in g/cm3
|
2146
|
+
|
2147
|
+
"""
|
2148
|
+
# Convert inputs to numpy arrays if they are not already
|
2149
|
+
if isinstance(P_kbar, (int, float)):
|
2150
|
+
P_kbar = np.array([P_kbar])
|
2151
|
+
elif isinstance(P_kbar, (pd.Series, list)):
|
2152
|
+
P_kbar = np.array(P_kbar)
|
2153
|
+
|
2154
|
+
if isinstance(T_K, (int, float)):
|
2155
|
+
T_K = np.array([T_K])
|
2156
|
+
elif isinstance(T_K, (pd.Series, list)):
|
2157
|
+
T_K = np.array(T_K)
|
2158
|
+
|
2159
|
+
# Ensure both arrays are the same shape
|
2160
|
+
P_kbar, T_K = np.broadcast_arrays(P_kbar, T_K)
|
2161
|
+
|
2162
|
+
P_Pa = P_kbar * 10**8
|
2163
|
+
|
2164
|
+
try:
|
2165
|
+
import CoolProp.CoolProp as cp
|
2166
|
+
except ImportError:
|
2167
|
+
raise RuntimeError('You havent installed CoolProp, which is required to convert FI densities to pressures. If you have python through conda, run conda install -c conda-forge coolprop in your command line')
|
2168
|
+
|
2169
|
+
H2O_dens_gcm3 = np.zeros_like(P_kbar, dtype=float)
|
2170
|
+
|
2171
|
+
non_zero_indices = P_kbar != 0
|
2172
|
+
if np.any(non_zero_indices):
|
2173
|
+
H2O_dens_gcm3[non_zero_indices] = cp.PropsSI('D', 'P', P_Pa[non_zero_indices], 'T', T_K[non_zero_indices], 'H2O') / 1000
|
2174
|
+
|
2175
|
+
return pd.Series(H2O_dens_gcm3)
|
2176
|
+
|
2177
|
+
|
2178
|
+
|
2179
|
+
|
2180
|
+
|
2181
|
+
|
2182
|
+
|
2183
|
+
|
2184
|
+
|
870
2185
|
|
871
2186
|
|
872
2187
|
|