DiadFit 0.0.90__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 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), or 'SP94' for Sterner and Pitzer (1994)
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), or 'SP94' for Sterner and Pitzer (1994)
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 SW96 as an EOS')
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