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_H2O_EOS.py DELETED
@@ -1,1255 +0,0 @@
1
- import numpy as np
2
- import matplotlib.pyplot as plt
3
- import pandas as pd
4
- import inspect
5
- from scipy.interpolate import CubicSpline
6
- import scipy
7
- from scipy.optimize import minimize
8
-
9
- from pathlib import Path
10
- from pickle import load
11
- import pickle
12
- import math
13
- from DiadFit.CO2_EOS import *
14
-
15
-
16
- DiadFit_dir=Path(__file__).parent
17
-
18
- # Set up constants.
19
- Tc1 = 647.25
20
- Pc1 = 221.19
21
- Tc2 = 301.1282
22
- Pc2 = 73.773
23
-
24
- # Set up low pressure and high pressure parameters for CO2.
25
-
26
- aL1 = [0] * 16 # Assuming the array is 1-indexed like in C.
27
- #So we dont need to adjust everything
28
-
29
- aL1[1] = 4.38269941 / 10**2
30
- aL1[2] = -1.68244362 / 10**1
31
- aL1[3] = -2.36923373 / 10**1
32
- aL1[4] = 1.13027462 / 10**2
33
- aL1[5] = -7.67764181 / 10**2
34
- aL1[6] = 9.71820593 / 10**2
35
- aL1[7] = 6.62674916 / 10**5
36
- aL1[8] = 1.06637349 / 10**3
37
- aL1[9] = -1.23265258 / 10**3
38
- aL1[10] = -8.93953948 / 10**6
39
- aL1[11] = -3.88124606 / 10**5
40
- aL1[12] = 5.61510206 / 10**5
41
- aL1[13] = 7.51274488 / 10**3 # alpha for H2O
42
- aL1[14] = 2.51598931 # beta for H2O
43
- aL1[15] = 3.94 / 10**2 # gamma for H2O
44
-
45
- # Higher pressure parameters - 0.2- 1 GPa
46
- aH1 = [0] * 16 # Assuming the array is 1-indexed like in C
47
-
48
- aH1[1] = 4.68071541 / 10**2
49
- aH1[2] = -2.81275941 / 10**1
50
- aH1[3] = -2.43926365 / 10**1
51
- aH1[4] = 1.10016958 / 10**2
52
- aH1[5] = -3.86603525 / 10**2
53
- aH1[6] = 9.30095461 / 10**2
54
- aH1[7] = -1.15747171 / 10**5
55
- aH1[8] = 4.19873848 / 10**4
56
- aH1[9] = -5.82739501 / 10**4
57
- aH1[10] = 1.00936000 / 10**6
58
- aH1[11] = -1.01713593 / 10**5
59
- aH1[12] = 1.63934213 / 10**5
60
- aH1[13] = -4.49505919 / 10**2 # alpha for H2O
61
- aH1[14] = -3.15028174 / 10**1 # beta for H2O
62
- aH1[15] = 1.25 / 10**2 # gamma for H2O
63
-
64
-
65
- # Low presure CO2 parameters.
66
-
67
- aL2 = [0] * 16 # Assuming the array is 1-indexed like in C
68
-
69
- aL2[1] = 1.14400435 / 10**1
70
- aL2[2] = -9.38526684 / 10**1
71
- aL2[3] = 7.21857006 / 10**1
72
- aL2[4] = 8.81072902 / 10**3
73
- aL2[5] = 6.36473911 / 10**2
74
- aL2[6] = -7.70822213 / 10**2
75
- aL2[7] = 9.01506064 / 10**4
76
- aL2[8] = -6.81834166 / 10**3
77
- aL2[9] = 7.32364258 / 10**3
78
- aL2[10] = -1.10288237 / 10**4
79
- aL2[11] = 1.26524193 / 10**3
80
- aL2[12] = -1.49730823 / 10**3
81
- aL2[13] = 7.81940730 / 10**3 # alpha for CO2
82
- aL2[14] = -4.22918013 # beta for CO2
83
- aL2[15] = 1.585 / 10**1 # gamma for CO2
84
-
85
-
86
-
87
- # High pressure CO2 parameters.
88
- aH2 = [0] * 16 # Assuming the array is 1-indexed like in C
89
- aH2[1] = 5.72573440 / 10**3
90
- aH2[2] = 7.94836769
91
- aH2[3] = -3.84236281 * 10.0
92
- aH2[4] = 3.71600369 / 10**2
93
- aH2[5] = -1.92888994
94
- aH2[6] = 6.64254770
95
- aH2[7] = -7.02203950 / 10**6
96
- aH2[8] = 1.77093234 / 10**2
97
- aH2[9] = -4.81892026 / 10**2
98
- aH2[10] = 3.88344869 / 10**6
99
- aH2[11] = -5.54833167 / 10**4
100
- aH2[12] = 1.70489748 / 10**3
101
- aH2[13] = -4.13039220 / 10**1 # alpha for CO2
102
- aH2[14] = -8.47988634 # beta for CO2
103
- aH2[15] = 2.800 / 10**2 # gamma for CO2
104
-
105
-
106
-
107
- import pandas as pd
108
- import numpy as np
109
-
110
- def ensure_series(a, b, c):
111
- # Determine the target length
112
- lengths = [len(a) if isinstance(a, (pd.Series, np.ndarray)) else None,
113
- len(b) if isinstance(b, (pd.Series, np.ndarray)) else None,
114
- len(c) if isinstance(c, (pd.Series, np.ndarray)) else None]
115
- lengths = [l for l in lengths if l is not None]
116
- target_length = max(lengths) if lengths else 1
117
-
118
- # Convert each input to a Series of the target length
119
- if not isinstance(a, (pd.Series, np.ndarray)):
120
- a = pd.Series([a] * target_length)
121
- else:
122
- a = pd.Series(a)
123
-
124
- if not isinstance(b, (pd.Series, np.ndarray)):
125
- b = pd.Series([b] * target_length)
126
- else:
127
- b = pd.Series(b)
128
-
129
- if not isinstance(c, (pd.Series, np.ndarray)):
130
- c = pd.Series([c] * target_length)
131
- else:
132
- c = pd.Series(c)
133
-
134
- return a, b, c
135
-
136
-
137
- def ensure_series_4(a, b, c, d):
138
- # Determine the target length
139
- lengths = [len(a) if isinstance(a, (pd.Series, np.ndarray)) else None,
140
- len(b) if isinstance(b, (pd.Series, np.ndarray)) else None,
141
- len(c) if isinstance(c, (pd.Series, np.ndarray)) else None,
142
- len(d) if isinstance(d, (pd.Series, np.ndarray)) else None]
143
- lengths = [l for l in lengths if l is not None]
144
- target_length = max(lengths) if lengths else 1
145
-
146
- # Convert each input to a Series of the target length
147
- if not isinstance(a, (pd.Series, np.ndarray)):
148
- a = pd.Series([a] * target_length)
149
- else:
150
- a = pd.Series(a)
151
-
152
- if not isinstance(b, (pd.Series, np.ndarray)):
153
- b = pd.Series([b] * target_length)
154
- else:
155
- b = pd.Series(b)
156
-
157
- if not isinstance(c, (pd.Series, np.ndarray)):
158
- c = pd.Series([c] * target_length)
159
- else:
160
- c = pd.Series(c)
161
-
162
- if not isinstance(d, (pd.Series, np.ndarray)):
163
- d = pd.Series([d] * target_length)
164
- else:
165
- d = pd.Series(d)
166
-
167
- return a, b, c, d
168
-
169
-
170
-
171
- ## Pure EOS functions
172
- # First, we need the pure EOS
173
- def pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g):
174
- """
175
- This function calculates the compressability factor for a pure EOS using the modified Lee-Kesler
176
- equation.
177
-
178
- i=0 for H2O, i=1 for CO2.
179
-
180
- You input a volume, and it returns the difference between the compresability factor, and that calculated at the input P, V and T_K.
181
- E.g. gives the residual so that you can iterate.
182
- """
183
- CF = (1.0 + (B[i] * Vc[i] / V) + (C[i] * Vc[i] *
184
- Vc[i] / (V * V)) + (D[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
185
- (V * V * V * V)))
186
-
187
- CF += ((E[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
188
- (V * V * V * V * V)))
189
-
190
- CF += ((F[i] * Vc[i] * Vc[i] / (V * V)) *
191
- (b[i] + g[i] * Vc[i] * Vc[i] / (V * V)) *
192
- math.exp(-g[i] * Vc[i] * Vc[i] / (V * V)))
193
-
194
- return CF - (P * V) / (83.14467 * TK)
195
-
196
- def pureEOS_CF(i, V, P, B, C, D, E, F, Vc, TK, b, g):
197
- """
198
- This function calculates the compressability factor for a pure EOS using the modified Lee-Kesler
199
- equation.
200
-
201
- i=0 for H2O, i=1 for CO2.
202
-
203
- You input a volume, and it returns the difference between the compresability factor, and that calculated at the input P, V and T_K.
204
- E.g. gives the residual so that you can iterate.
205
- """
206
- CF = (1.0 + (B[i] * Vc[i] / V) + (C[i] * Vc[i] *
207
- Vc[i] / (V * V)) + (D[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
208
- (V * V * V * V)))
209
-
210
- CF += ((E[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] /
211
- (V * V * V * V * V)))
212
-
213
- CF += ((F[i] * Vc[i] * Vc[i] / (V * V)) *
214
- (b[i] + g[i] * Vc[i] * Vc[i] / (V * V)) *
215
- math.exp(-g[i] * Vc[i] * Vc[i] / (V * V)))
216
-
217
- return CF
218
-
219
-
220
- # Volume iterative function using Netwon-Raphson method.
221
- def purevolume(i, V, P, B, C, D, E, F, Vc, TK, b, g):
222
- """ Using the pure EOS, this function solves for the best molar volume (in cm3/mol) using the pureEOS residual calculated above
223
-
224
- It returns the volume.
225
-
226
- """
227
- for iter in range(1, 51):
228
- # Calculate the derivative of the pureEOS function at (V, P)
229
- 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
230
-
231
- # Update the volume using the Newton-Raphson method
232
- Vnew = V - pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g) / diff
233
-
234
- # Check if the update is within the tolerance (0.000001)
235
- if abs(Vnew - V) <= 0.000001:
236
- break
237
-
238
- # Update V for the next iteration
239
- V = Vnew
240
-
241
- # Return the final estimated volume
242
- return V
243
-
244
- def purepressure(i, V, P, TK):
245
- """ Using the pure EOS, this function solves for the best pressure (in bars) using the pureEOS residual calculated above
246
-
247
- It returns the pressure in bars
248
-
249
- """
250
- for iter in range(1, 51):
251
- # Calculate the derivative of the pureEOS function at (V, P)
252
- k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
253
-
254
- 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
255
-
256
- # Update the pressure using the Newton-Raphson method
257
- Pnew = P - pureEOS(i, V, P, B, C, D, E, F, Vc, TK, b, g) / diff
258
-
259
- # Dont allow negative solutions
260
- if Pnew < 0:
261
- Pnew = 30000
262
-
263
- # Check if the update is within the tolerance (0.000001)
264
- if abs(Pnew - P) <= 0.000001:
265
- break
266
-
267
- # Update P for the next iteration
268
- P = Pnew
269
-
270
- # Return the final estimated pressure
271
- return P
272
-
273
-
274
-
275
-
276
- def mol_vol_to_density(mol_vol, XH2O):
277
- """ Converts molar volume (cm3/mol) to density (g/cm3) for a given XH2O"""
278
- density=((1-XH2O)*44 + (XH2O)*18)/mol_vol
279
- return density
280
-
281
- def pure_lnphi(i, Z, B, Vc, V, C, D, E, F, g, b):
282
- """
283
- This function calculates the fugacity coefficient (kbar) from the equation of state for a pure fluid
284
-
285
- """
286
- 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]))
287
- lnph += (D[i] * Vc[i] * Vc[i] * Vc[i] * Vc[i] / (4.0 * V[i] * V[i] * V[i] * V[i]))
288
- 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]))
289
- 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])))
290
-
291
- return lnph
292
-
293
-
294
-
295
- ## Mixing between species
296
- def cbrt_calc(x):
297
- """
298
- This function calculates the cubic root that can deal with negative numbers.
299
- """
300
- if x >= 0:
301
- return math.pow(x, 1/3)
302
- else:
303
- return -math.pow(-x, 1/3)
304
-
305
-
306
- def mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK):
307
- """ This function is like the one for the pureEOS. It calculates the compressability factor, and
308
- then calculates the compressability factor based on the P-V-T you entered. It returns the residual between those two values.
309
- """
310
- CF = 1.0 + (BVc / V) + (CVc2 / (V * V)) + (DVc4 / (V * V * V * V)) + (EVc5 / (V * V * V * V * V))
311
- CF += (FVc2 / (V * V)) * (bmix + gVc2 / (V * V)) * np.exp(-gVc2 / (V * V))
312
-
313
- return CF - (P * V) / (83.14467 * TK)
314
-
315
-
316
- def mixEOS_CF(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK):
317
- """ This function is like the one for the pureEOS. It calculates the compressability factor, and
318
- then calculates that based on the P-V-T you entered. It does not return the residual
319
- """
320
- CF = 1.0 + (BVc / V) + (CVc2 / (V * V)) + (DVc4 / (V * V * V * V)) + (EVc5 / (V * V * V * V * V))
321
- CF += (FVc2 / (V * V)) * (bmix + gVc2 / (V * V)) * np.exp(-gVc2 / (V * V))
322
-
323
- return CF
324
-
325
-
326
- def mixvolume(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK):
327
- """ This function iterates in volume space to get the best match volume (cm3/mol) to the entered pressure using the mixEOS function above.
328
-
329
- """
330
- for iter in range(1, 51):
331
- diff = ((mixEOS(V + 0.0001, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)
332
- - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)) / 0.0001)
333
- Vnew = V - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK) / diff
334
- if abs(Vnew - V) <= 0.000001:
335
- break
336
- V = Vnew
337
-
338
- return V
339
-
340
- import warnings as w
341
-
342
- ## We are going to have to use a look up table to help the netwon raphson converge.
343
-
344
- # Load the lookup table from the CSV file
345
- DiadFit_dir=Path(__file__).parent
346
- file_str='lookup_table_noneg.csv'
347
- dz06_lookuptable=pd.read_csv(DiadFit_dir/file_str)
348
- #df = pd.read_csv('lookup_table_noneg.csv')
349
-
350
-
351
-
352
-
353
-
354
- def get_initial_guess(V_target, T_K_target, XH2O_target):
355
- # Calculate the Euclidean distance from the target point to all points in the table
356
- # We normalize each dimension by its range to give equal weight to all parameters
357
-
358
-
359
- df=dz06_lookuptable
360
-
361
- # code to find best value
362
-
363
- P_range = df['P_kbar'].max() - df['P_kbar'].min()
364
- T_K_range = df['T_K'].max() - df['T_K'].min()
365
- XH2O_range = df['XH2O'].max() - df['XH2O'].min()
366
- V_range = df['V'].max() - df['V'].min()
367
-
368
- # Calculate normalized distances
369
- distances = np.sqrt(
370
- ((df['P_kbar'] - df['P_kbar'].mean()) / P_range) ** 2 +
371
- ((df['T_K'] - T_K_target) / T_K_range) ** 2 +
372
- ((df['XH2O'] - XH2O_target) / XH2O_range) ** 2 +
373
- ((df['V'] - V_target) / V_range) ** 2
374
- )
375
-
376
- # Find the index of the closest row in the DataFrame
377
- closest_index = distances.argmin()
378
-
379
- # Retrieve the P_kbar value from the closest row
380
- initial_guess_P = df.iloc[closest_index]['P_kbar']
381
-
382
-
383
-
384
- return initial_guess_P
385
-
386
-
387
-
388
-
389
- def mixpressure(P, V, TK, Y):
390
- """ 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.
391
-
392
- """
393
-
394
-
395
- for iter in range(1, 51):
396
- k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
397
- 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)
398
-
399
- diff = ((mixEOS(V, P + 0.0001, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)
400
- - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK)) / 0.0001)
401
- Pnew = P - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK) / diff
402
-
403
-
404
-
405
-
406
- if abs(Pnew - P) <= 0.000001:
407
- break
408
-
409
- P = Pnew
410
-
411
- return P
412
-
413
- # # Dont allow negative solutions
414
- # if Pnew<0:
415
- # Pnew = 3000
416
-
417
- # if Pnew < 10000 and Pnew>0 and V<50:
418
- # 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')
419
- #
420
- # Pnew = 10000 # Replace 0.0001 with a small positive value that makes sense for your system
421
- #
422
-
423
-
424
- # def mixpressure(P_init, V, TK, Y, max_restarts=3):
425
- # """This function iterates in pressure space to get the best match to the entered volume using the mixEOS function above."""
426
- #
427
- # restarts = 0
428
- # while restarts <= max_restarts:
429
- # P = P_init
430
- # for iter in range(1, 51):
431
- # # Your EOS parameters and mixing rules calculations here
432
- #
433
- # 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)
434
- # Pnew = P - mixEOS(V, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, TK) / diff
435
- #
436
- # # Don't allow unrealistic solutions but provide a chance to reset
437
- # if Pnew < 5000 and V > 10:
438
- # warnings.warn('Root forced above 5000 bars due to conditions, attempting restart...')
439
- # Pnew = 5000 # Force the pressure up due to your condition
440
- # break # Break out of the current iteration loop to allow for a restart
441
- #
442
- # if abs(Pnew - P) <= 0.000001: # Convergence criterion
443
- # return Pnew # Return the converged value
444
- # P = Pnew
445
- #
446
- # restarts += 1 # Increment the number of restarts attempted
447
- # P_init = 5000 # Set a new starting point that might be closer to the suspected real root
448
- #
449
- # warnings.warn('Max restarts reached, solution may not be optimal.')
450
- # return P # Return the last computed pressure if all restart attempts fail
451
-
452
-
453
-
454
-
455
-
456
-
457
- def mix_lnphi(i, Zmix, BVc_prm, CVc2_prm, DVc4_prm, EVc5_prm, FVc2_prm, FVc2, bmix, b_prm, gVc2, gVc2_prm, Vmix):
458
- """ This function calculates lnphi values"""
459
- lnph=0
460
-
461
- lnph = -math.log(Zmix)
462
- lnph += (BVc_prm[i] / Vmix)
463
- lnph += (CVc2_prm[i] / (2.0 * Vmix ** 2))
464
- lnph += (DVc4_prm[i] / (4.0 * Vmix ** 4))
465
- lnph += (EVc5_prm[i] / (5.0 * Vmix ** 5))
466
- lnph += ((FVc2_prm[i] * bmix + b_prm[i] * FVc2) / (2 * gVc2)) * (1.0 - math.exp(-gVc2 / (Vmix ** 2)))
467
- 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)))
468
- 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)))
469
-
470
-
471
- return lnph
472
-
473
-
474
-
475
- def mix_fugacity_ind(*, P_kbar, T_K, XH2O, Vmix):
476
- """ This function calculates fugacity for a single sample.
477
- It returns the activity of each component (fugacity/fugacity in pure component)
478
-
479
-
480
- """
481
-
482
- P=P_kbar*1000
483
- TK=T_K
484
- XCO2=1-XH2O
485
- Y = [0] * 2
486
- Y[0]=XH2O
487
- Y[1]=XCO2
488
-
489
- # Calculate the constants you neeed
490
- k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
491
-
492
-
493
- lnphi2kbL = [0.0, 0.0]
494
- lnphi2kbH = [0.0, 0.0]
495
-
496
- lnphi = [0.0, 0.0]
497
- phi_mix = [0.0, 0.0]
498
- activity = [0.0, 0.0]
499
- f = [0.0, 0.0]
500
-
501
- # Calculate at pressure of interest
502
- Z_pure = [0.0, 0.0]
503
- V_pure = [0.0, 0.0]
504
-
505
-
506
- # Initial guess for volumne
507
-
508
- if P<=2000:
509
- Vguess=1000
510
- elif P>20000:
511
- Vguess=10
512
- else:
513
- Vguess=100
514
-
515
- V_pure[1]=purevolume(1, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
516
- V_pure[0]=purevolume(0, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
517
- Z_pure[0]=P*V_pure[0]/(83.14467*TK)
518
- Z_pure[1]=P*V_pure[1]/(83.14467*TK)
519
-
520
-
521
- #H2O pure
522
- lnphi0=pure_lnphi(0, Z_pure, B, Vc, V_pure, C, D, E, F, g, b)
523
- #CO2 pure
524
- lnphi1=pure_lnphi(1, Z_pure, B, Vc, V_pure, C, D, E, F, g, b)
525
-
526
-
527
- # Funny maths you have to do incase P>2000 bars
528
-
529
-
530
- # First, calculate parameters with low pressure coefficients
531
- 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)
532
- Z_pure_LP_2000 = [0.0, 0.0]
533
- V_pure_LP_2000 = [0.0, 0.0]
534
- 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)
535
- 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)
536
-
537
- Z_pure_LP_2000[0]=2000.0*V_pure_LP_2000[0]/(83.14467*TK)
538
- Z_pure_LP_2000[1]=2000.0*V_pure_LP_2000[1]/(83.14467*TK)
539
-
540
- # Low pressure
541
- 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)
542
- 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)
543
-
544
-
545
-
546
- # Same with high P
547
- 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)
548
- Z_pure_HP_2000 = [0.0, 0.0]
549
- V_pure_HP_2000 = [0.0, 0.0]
550
- 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)
551
- 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)
552
- Z_pure_HP_2000[0]=2000.0*V_pure_HP_2000[0]/(83.14467*TK)
553
- Z_pure_HP_2000[1]=2000.0*V_pure_HP_2000[1]/(83.14467*TK)
554
-
555
-
556
- #pure_HP
557
- 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)
558
- 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)
559
-
560
- if P>2000:
561
- # This is a weird thing described on Page 6 of Yoshimura -
562
- lnphi0=lnphi0-lnphi0_HP+lnphi0_LP
563
- lnphi1=lnphi1-lnphi1_HP+lnphi1_LP
564
-
565
- phi0_pure=math.exp(lnphi0)
566
- phi1_pure=math.exp(lnphi1)
567
-
568
-
569
-
570
- # Now we need to do the mixed fugacity part of this
571
- #--------------------------------------------------------------------------
572
- k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
573
- 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)
574
- Zmix=(P*Vmix)/(83.14467*TK)
575
- lnphi_mix = [0.0, 0.0]
576
- phi_mix = [0.0, 0.0]
577
- 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)
578
- 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)
579
-
580
-
581
-
582
- # But what if P>2000, well we need to do these calcs at low and high P
583
- # High P - using Parameters from up above
584
- 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)
585
- Vmix_HP=mixvolume(100, 2000, BVc_HP, CVc2_HP, DVc4_HP, EVc5_HP, FVc2_HP, bmix_HP, gVc2_HP, TK)
586
- Zmix_HP=(2000*Vmix_HP)/(83.14467*TK)
587
- lnphi_mix_HP = [0.0, 0.0]
588
- 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)
589
- 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)
590
-
591
-
592
- # Same for LP
593
- 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)
594
- Vmix_LP=mixvolume(100, 2000, BVc_LP, CVc2_LP, DVc4_LP, EVc5_LP, FVc2_LP, bmix_LP, gVc2_LP, TK)
595
- Zmix_LP=(2000*Vmix_LP)/(83.14467*TK)
596
- lnphi_mix_LP = [0.0, 0.0]
597
- 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)
598
- 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)
599
-
600
- if P>2000:
601
- lnphi_mix[0]=lnphi_mix[0]-lnphi_mix_HP[0]+lnphi_mix_LP[0]
602
- lnphi_mix[1]=lnphi_mix[1]-lnphi_mix_HP[1]+lnphi_mix_LP[1]
603
-
604
-
605
- phi_mix[0]=math.exp(lnphi_mix[0])
606
- phi_mix[1]=math.exp(lnphi_mix[1])
607
-
608
-
609
-
610
-
611
-
612
-
613
-
614
- activity[0] = phi_mix[0] * Y[0] / phi0_pure
615
- activity[1] = phi_mix[1] * Y[1] / phi1_pure
616
-
617
- f[0] = Y[0] * P * phi_mix[0] / 1000.0 # fugacity in kbar
618
- f[1] = Y[1] * P * phi_mix[1] / 1000.0 # fugacity in kbar
619
-
620
-
621
-
622
-
623
- return f[0], f[1], activity[0], activity[1], Zmix
624
-
625
-
626
-
627
-
628
-
629
- def mixing_rules(B, C, D, E, F, Vc, Y, b, g, k1_temperature, k2_temperature, k3_temperature):
630
- """ This function applies the DZ06 mixing rules"""
631
-
632
- Bij = np.zeros((2, 2))
633
- Vcij = np.zeros((2, 2))
634
- BVc_prm = np.zeros(2)
635
- b_prm=np.zeros(2)
636
- BVc = 0.0
637
- Cijk = np.zeros((2, 2, 2))
638
- Vcijk = np.zeros((2, 2, 2))
639
- CVc2_prm = np.zeros(2)
640
- CVc2 = 0.0
641
- Dijklm = np.zeros((2, 2, 2, 2, 2))
642
- Vcijklm = np.zeros((2, 2, 2, 2, 2))
643
- Eijklmn=np.zeros((2, 2, 2, 2, 2, 2))
644
- Vcijklmn=np.zeros((2, 2, 2, 2, 2, 2))
645
- DVc4_prm=np.zeros(2)
646
- DVc4=0
647
-
648
- EVc5_prm = np.zeros(2)
649
- EVc5 = 0.0
650
- Fij = np.zeros((2, 2))
651
- FVc2_prm = np.zeros(2)
652
- FVc2 = 0.0
653
- gijk = np.zeros((2, 2, 2))
654
- gVc2_prm = np.zeros(2)
655
- gVc2 = 0.0
656
-
657
- for i in range(2):
658
- for j in range(2):
659
- k1 = 1.0 if i == j else k1_temperature
660
- Bij[i, j] = pow((cbrt_calc(B[i]) + cbrt_calc(B[j]))/2, 3.0) * k1
661
-
662
- for i in range(2):
663
- for j in range(2):
664
- Vcij[i, j] = pow((cbrt_calc(Vc[i]) + cbrt_calc(Vc[j]))/2, 3.0)
665
-
666
-
667
- for i in range(2):
668
- for j in range(2):
669
- BVc_prm[i] += 2 * Y[j] * Bij[i, j] * Vcij[i, j]
670
-
671
-
672
-
673
- for i in range(2):
674
- for j in range(2):
675
-
676
- BVc += Y[i] * Y[j] * Bij[i, j] * Vcij[i, j]
677
-
678
- for i in range(2):
679
- for j in range(2):
680
- for k in range(2):
681
- k2 = 1.0 if i == j and j == k else k2_temperature
682
- Cijk[i, j, k] = pow((cbrt_calc(C[i]) + cbrt_calc(C[j]) + cbrt_calc(C[k]))/3, 3.0) * k2
683
-
684
- for i in range(2):
685
- for j in range(2):
686
- for k in range(2):
687
- Vcijk[i, j, k] = pow((cbrt_calc(Vc[i]) + cbrt_calc(Vc[j]) + cbrt_calc(Vc[k]))/3, 3.0)
688
-
689
- for i in range(2):
690
- for j in range(2):
691
- for k in range(2):
692
- CVc2_prm[i] += 3 * Y[j] * Y[k] * Cijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
693
-
694
- CVc2=0.0
695
- for i in range(2):
696
- for j in range(2):
697
- for k in range(2):
698
- CVc2 += Y[i] * Y[j] * Y[k] * Cijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
699
-
700
-
701
- for i in range(2):
702
- for j in range(2):
703
- for k in range(2):
704
- for l in range(2):
705
- for m in range(2):
706
- 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)
707
-
708
- for i in range(2):
709
- for j in range(2):
710
- for k in range(2):
711
- for l in range(2):
712
- for m in range(2):
713
- 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)
714
-
715
-
716
- for i in range(2):
717
- for j in range(2):
718
- for k in range(2):
719
- for l in range(2):
720
- for m in range(2):
721
- 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)
722
-
723
-
724
- for i in range(2):
725
- for j in range(2):
726
- for k in range(2):
727
- for l in range(2):
728
- for m in range(2):
729
- 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)
730
-
731
- # Missing Eijklmn,
732
- for i in range(2):
733
- for j in range(2):
734
- for k in range(2):
735
- for l in range(2):
736
- for m in range(2):
737
- for n in range(2):
738
- 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)
739
-
740
-
741
- for i in range(2):
742
- for j in range(2):
743
- for k in range(2):
744
- for l in range(2):
745
- for m in range(2):
746
- for n in range(2):
747
- 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)
748
-
749
-
750
-
751
- for i in range(2):
752
- for j in range(2):
753
- for k in range(2):
754
- for l in range(2):
755
- for m in range(2):
756
- for n in range(2):
757
- 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)
758
-
759
-
760
- for i in range(2):
761
- for j in range(2):
762
- for k in range(2):
763
- for l in range(2):
764
- for m in range(2):
765
- for n in range(2):
766
- 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)
767
-
768
- for i in range(2):
769
- for j in range(2):
770
- Fij[i, j] = pow((cbrt_calc(F[i]) + cbrt_calc(F[j]))/2, 3.0)
771
-
772
- for i in range(2):
773
- for j in range(2):
774
- FVc2_prm[i] += 2.0 * Y[j] * Fij[i, j] * Vcij[i, j] * Vcij[i, j]
775
-
776
- for i in range(2):
777
- for j in range(2):
778
- FVc2 += Y[i] * Y[j] * Fij[i, j] * Vcij[i, j] * Vcij[i, j]
779
-
780
- bmix = Y[0] * b[0] + Y[1] * b[1]
781
-
782
- b_prm[0] = b[0]
783
- b_prm[1] = b[1]
784
-
785
- for i in range(2):
786
- for j in range(2):
787
- for k in range(2):
788
- k3 = 1.0 if i == j and j == k else k3_temperature
789
- gijk[i, j, k] = pow((cbrt_calc(g[i]) + cbrt_calc(g[j]) + cbrt_calc(g[k]))/3, 3.0) * k3
790
-
791
- for i in range(2):
792
- for j in range(2):
793
- for k in range(2):
794
- gVc2_prm[i] += 3.0 * Y[j] * Y[k] * gijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
795
-
796
-
797
- for i in range(2):
798
- for j in range(2):
799
- for k in range(2):
800
- gVc2 += Y[i] * Y[j] * Y[k] * gijk[i, j, k] * Vcijk[i, j, k] * Vcijk[i, j, k]
801
-
802
-
803
- 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
804
-
805
-
806
- ## Getting EOS contsants themselves
807
-
808
- def get_EOS_params(P, TK):
809
- """ This function returns the EOS 'constants' if you know the pressure (in bars) and temperature (in Kelvin)
810
-
811
- """
812
-
813
- a1 = np.zeros(16)
814
- a2 = np.zeros(16)
815
- b = np.zeros(2)
816
- g = np.zeros(2)
817
- Vc = np.zeros(1)
818
- B = np.zeros(2)
819
- C = np.zeros(2)
820
- D = np.zeros(2)
821
- E = np.zeros(2)
822
- F = np.zeros(2)
823
- V = np.zeros(2)
824
- Vc = np.zeros(2)
825
-
826
-
827
-
828
-
829
-
830
-
831
-
832
-
833
- # Initial guess for volumne
834
-
835
- if P<=2000:
836
- Vguess=1000
837
- elif P>20000:
838
- Vguess=10
839
- else:
840
- Vguess=100
841
-
842
-
843
- if P <= 2000.0:
844
- for i in range(16):
845
- a1[i] = aL1[i]
846
- a2[i] = aL2[i]
847
- # These are the binary interaction parameters
848
- k1_temperature = 3.131 - (5.0624 / 10**3.0) * TK + (1.8641 / 10**6) * TK**2 - 31.409 / TK
849
- k2_temperature = -46.646 + (4.2877 / 10**2.0) * TK - (1.0892 / 10**5) * TK**2 + 1.5782 * 10**4 / TK
850
- k3_temperature = 0.9
851
- else:
852
- for i in range(16):
853
- a1[i] = aH1[i]
854
- a2[i] = aH2[i]
855
- # Same, but for higher pressures
856
- k1_temperature = 9.034 - (7.9212 / 10**3) * TK + (2.3285 / 10**6) * TK**2 - 2.4221 * 10**3 / TK
857
- k2_temperature = -1.068 + (1.8756 / 10**3) * TK - (4.9371 / 10**7) * TK**2 + 6.6180 * 10**2 / TK
858
- k3_temperature = 1.0
859
-
860
- b[0] = a1[14] # beta for H2O
861
- b[1] = a2[14] # beta for CO2
862
- g[0] = a1[15] # gamma for H2O
863
- g[1] = a2[15] # gamma for CO2
864
-
865
- Vc[0] = 83.14467 * Tc1 / Pc1
866
- B[0] = a1[1] + a1[2] / ((TK / Tc1) * (TK / Tc1)) + a1[3] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
867
- C[0] = a1[4] + a1[5] / ((TK / Tc1) * (TK / Tc1)) + a1[6] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
868
- D[0] = a1[7] + a1[8] / ((TK / Tc1) * (TK / Tc1)) + a1[9] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
869
- E[0] = a1[10] + a1[11] / ((TK / Tc1) * (TK / Tc1)) + a1[12] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
870
- F[0] = a1[13] / ((TK / Tc1) * (TK / Tc1) * (TK / Tc1))
871
-
872
- Vc[1] = 83.14467 * Tc2 / Pc2
873
- B[1] = a2[1] + a2[2] / ((TK / Tc2) * (TK / Tc2)) + a2[3] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
874
- C[1] = a2[4] + a2[5] / ((TK / Tc2) * (TK / Tc2)) + a2[6] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
875
- D[1] = a2[7] + a2[8] / ((TK / Tc2) * (TK / Tc2)) + a2[9] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
876
- E[1] = a2[10] + a2[11] / ((TK / Tc2) * (TK / Tc2)) + a2[12] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
877
- F[1] = a2[13] / ((TK / Tc2) * (TK / Tc2) * (TK / Tc2))
878
- return k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess
879
-
880
- ## Lets wrap all these functions up.
881
-
882
- def calculate_molar_volume_ind_DZ2006(*, P_kbar, T_K, XH2O):
883
- """ This function calculates molar volume (cm3/mol) for a known pressure (kbar), T in K and XH2O (mol frac) for a single value
884
- """
885
-
886
- P=P_kbar*1000
887
- TK=T_K
888
-
889
- # Calculate the constants you neeed
890
- k1_temperature, k2_temperature, k3_temperature, a1, a2, g, b, Vc, B, C, D, E, F, Vguess=get_EOS_params(P, TK)
891
-
892
- if XH2O==0:
893
- mol_vol=purevolume(1, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
894
-
895
- if XH2O==1:
896
- mol_vol=purevolume(0, Vguess, P, B, C, D, E, F, Vc, TK, b, g)
897
-
898
- else:
899
- XCO2=1-XH2O
900
- Y = [0] * 2
901
- Y[0]=XH2O
902
- Y[1]=XCO2
903
- 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)
904
-
905
-
906
- mol_vol=mixvolume(Vguess, P, BVc, CVc2, DVc4, EVc5, FVc2, bmix, gVc2, T_K)
907
-
908
- if mol_vol<0:
909
- mol_vol=np.nan
910
-
911
- return mol_vol
912
-
913
-
914
- def calculate_molar_volume_DZ2006(*, P_kbar, T_K, XH2O):
915
- """ Used to calculate molar volume (cm3/mol) in a loop for multiple inputs
916
-
917
-
918
- """
919
-
920
- P_kbar, T_K, XH2O=ensure_series(P_kbar, T_K, XH2O)
921
-
922
- # Check all the same length
923
- lengths = [len(P_kbar), len(T_K), len(XH2O)]
924
- if len(set(lengths)) != 1:
925
- raise ValueError("All input Pandas Series must have the same length.")
926
-
927
- # Set up loop
928
- mol_vol=np.zeros(len(P_kbar), float)
929
-
930
- for i in range(0, len(P_kbar)):
931
- 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))
932
-
933
-
934
-
935
-
936
-
937
- return mol_vol
938
-
939
- def calculate_Pressure_ind_DZ2006(*, mol_vol, T_K, XH2O, Pguess=None):
940
- """ 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.
941
- """
942
- V=mol_vol
943
- # if Pguess is None:
944
- # if V>1000:
945
- # Pguess=1000
946
- # elif V<10:
947
- # Pguess=20000
948
- # else:
949
- # Pguess=200
950
-
951
- # Lets get P guess from a look up table
952
- # uses a look up table
953
- Pguess=get_initial_guess(V_target=V, T_K_target=T_K, XH2O_target=XH2O)*1000
954
-
955
- if Pguess <= 0:
956
- return np.nan
957
-
958
-
959
- TK=T_K
960
-
961
- # lets do for low pressure initially
962
-
963
-
964
- # if XH2O==0:
965
- # P=purepressure(1, V, Pguess, TK)
966
- #
967
- # elif XH2O==1:
968
- # P=purepressure(0, V, Pguess, TK)
969
- #
970
- # else:
971
- XCO2=1-XH2O
972
- Y = [0] * 2
973
- Y[0]=XH2O
974
- Y[1]=XCO2
975
-
976
- # Now iteratively solve for pressure starting from this initial guess.
977
-
978
- P=mixpressure(Pguess, V, T_K, Y)
979
-
980
- return P
981
-
982
- def calculate_Pressure_DZ2006(*, mol_vol=None, density=None, T_K, XH2O):
983
- """ Used to calculate pressure in a loop for multiple inputs. Dens - bulk density.
984
-
985
-
986
- """
987
- # Make all a panda series
988
-
989
-
990
-
991
- if mol_vol is None and density is not None:
992
- mol_vol=density_to_mol_vol(density=density, XH2O=XH2O)
993
-
994
- mol_vol, T_K, XH2O=ensure_series(mol_vol, T_K, XH2O)
995
-
996
- # Check all the same length
997
- lengths = [len(mol_vol), len(T_K), len(XH2O)]
998
- if len(set(lengths)) != 1:
999
- raise ValueError("All input Pandas Series must have the same length.")
1000
-
1001
- # Set up loop
1002
- P=np.zeros(len(mol_vol), float)
1003
-
1004
- for i in range(0, len(mol_vol)):
1005
- 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))
1006
-
1007
-
1008
-
1009
- return P
1010
-
1011
-
1012
- def mix_fugacity(*, P_kbar, T_K, XH2O, Vmix):
1013
-
1014
- """ Used to calculate fugacity, compressability and activities for a panda series
1015
-
1016
- """
1017
- # Make everything a pandas series
1018
-
1019
- P_kbar, T_K, XH2O, Vmix=ensure_series_4(P_kbar, T_K, XH2O, Vmix)
1020
-
1021
-
1022
-
1023
- #Check all the same length
1024
- lengths = [len(P_kbar), len(T_K), len(XH2O), len(Vmix)]
1025
- if len(set(lengths)) != 1:
1026
- raise ValueError("All input Pandas Series must have the same length.")
1027
-
1028
- f=np.zeros(len(P_kbar), float)
1029
- a_CO2=np.zeros(len(P_kbar), float)
1030
- a_H2O=np.zeros(len(P_kbar), float)
1031
- f_CO2=np.zeros(len(P_kbar), float)
1032
- f_H2O=np.zeros(len(P_kbar), float)
1033
- Zmix=np.zeros(len(P_kbar), float)
1034
- for i in range(0, len(P_kbar)):
1035
-
1036
- 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))
1037
-
1038
- return f_H2O, f_CO2, a_H2O,a_CO2, Zmix
1039
-
1040
-
1041
- def mol_vol_to_density(*, mol_vol, XH2O):
1042
- """ Converts molar mass to density for a given XH2O"""
1043
- density=((1-XH2O)*44 + (XH2O)*18)/mol_vol
1044
- return density
1045
-
1046
- def density_to_mol_vol(*, density, XH2O):
1047
- """ Converts density in g/cm3 to molar volume for a given XH2O"""
1048
- mol_vol=((1-XH2O)*44 + (XH2O)*18)/density
1049
- return mol_vol
1050
-
1051
-
1052
-
1053
- def calc_prop_knownP_EOS_DZ2006(*, P_kbar=1, T_K=1200, XH2O=1):
1054
- """ This function calculates molar volume, density, compressability factor, fugacity, and activity for mixed H2O-CO2 fluids
1055
- using the EOS of Span and Wanger. It assumes you know P, T, and XH2O.
1056
-
1057
- Parameters
1058
- -------------------
1059
- P_kbar: float, np.array, pd.Series
1060
- Pressure in kbar
1061
- T_K: float, np.array, pd.Series
1062
- Temperature in Kelvin
1063
- XH2O: float, np.array, pd.Series
1064
- Molar fraction of H2O in the fluid phase.
1065
-
1066
- Returns
1067
- -------------------
1068
- pd.DataFrame
1069
-
1070
- """
1071
-
1072
-
1073
-
1074
- # First, check all pd Series
1075
-
1076
-
1077
- mol_vol=calculate_molar_volume_DZ2006(P_kbar=P_kbar, T_K=T_K, XH2O=XH2O)
1078
-
1079
-
1080
- f_H2O, f_CO2, a_H2O, a_CO2, Zmix=mix_fugacity(P_kbar=P_kbar, T_K=T_K, XH2O=XH2O,
1081
- Vmix=mol_vol)
1082
- density=mol_vol_to_density(mol_vol=mol_vol, XH2O=XH2O)
1083
- # 'T_K': T_K,
1084
- # 'P_kbar': P_kbar,
1085
- # 'XH2O': XH2O,
1086
- #
1087
-
1088
-
1089
- df=pd.DataFrame(data={'P_kbar': P_kbar,
1090
- 'T_K': T_K,
1091
- 'XH2O': XH2O,
1092
- 'XCO2': 1-XH2O,
1093
- 'Molar Volume (cm3/mol)': mol_vol,
1094
- 'Density (g/cm3)': density,
1095
- 'Compressability_factor': Zmix,
1096
- 'fugacity_H2O (kbar)': f_H2O,
1097
- 'fugacity_CO2 (kbar)': f_CO2,
1098
- 'activity_H2O': a_H2O,
1099
- 'activity_CO2': a_CO2})
1100
-
1101
- return df
1102
-
1103
-
1104
-
1105
- def calculate_entrapment_P_XH2O(*, XH2O, CO2_dens_gcm3, T_K, T_K_ambient=37+273.15, fast_calcs=False, Hloss=True):
1106
- """" This function calculates pressure for a measured CO$_2$ density, temperature and estimate of initial XH2O.
1107
- It first corrects the density to obtain a bulk density for a CO2-H2O mix, assuming that H2O was lost from the inclusion.
1108
- 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
1109
- pure CO2 equation of states for comparison
1110
-
1111
- Parameters
1112
- ----------------------
1113
- XH2O: float, pd.Series.
1114
- The molar fraction of H2O in the fluid. Should be between 0 and 1. Can get an estimate from say VESical.
1115
-
1116
- CO2_dens_gcm3: float, pd.Series
1117
- Measured CO2 density in g/cm3
1118
-
1119
- T_K: float, pd.Series
1120
- Temperature in Kelvin fluid was trapped at
1121
-
1122
- T_K_ambient: pd.Series
1123
- Temperature in Kelvin Raman measurement was made at.
1124
-
1125
- fast_calcs: bool (default False)
1126
- If True, only performs one EOS calc for DZ06, not 4 (with water, without water, SP94 and SW96).
1127
- also specify H2Oloss=True or False
1128
-
1129
-
1130
-
1131
- Returns
1132
- -----------------------------
1133
- if fast_calcs is False:
1134
- pd.DataFrame:
1135
- Columns showing:
1136
- P_kbar_pureCO2_SW96: Pressure calculated for the measured CO$_2$ density using the pure CO2 EOS from Span and Wanger (1996)
1137
- P_kbar_pureCO2_SP94: Pressure calculated for the measured CO$_2$ density using the pure CO2 EOS from Sterner and Pitzer (1994)
1138
- P_kbar_pureCO2_DZ06: Pressure calculated from the measured CO$_2$ density using the pure CO2 EOs from Duan and Zhang (2006)
1139
- 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
1140
- 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
1141
- P Mix_Hloss/P Pure DZ06: Correction factor - e.g. how much deeper the pressure is from the mixed EOS with H loss
1142
- P Mix_noHloss/P Pure DZ06: Correction factor - e.g. how much deeper the pressure is from the mixed EOS (assuming no H loss)
1143
- rho_mix_calc_noHloss: Bulk density calculated (C+H)
1144
- rho_mix_calc_Hloss: Bulk density calculated (C+H) after h loss
1145
- CO2_dens_gcm3: Input CO2 density
1146
- T_K: input temperature
1147
- XH2O: input molar fraction of H2O
1148
-
1149
- if fast_calcs is True:
1150
- P_kbar_mixCO2_DZ06: Pressure calculated from the reconstructed mixed fluid density using the mixed EOS from Duan and Zhang (2006)
1151
-
1152
-
1153
-
1154
- """
1155
- XH2O, rho_meas, T_K=ensure_series(a=XH2O, b=CO2_dens_gcm3, c=T_K)
1156
- alpha=XH2O/(1-XH2O)
1157
- # IF water is lost
1158
- rho_orig_H_loss=rho_meas*(1+alpha*(18/44))
1159
- # IF water isnt lost
1160
-
1161
- # Calculate mass ratio from molar ratio
1162
- XH2O_mass=(XH2O*18)/((1-XH2O)*44 +(XH2O*18) )
1163
- # Calculate pressure in CO2 fluid - returns answer in kbar
1164
- P_CO2=calculate_P_for_rho_T_SW96(CO2_dens_gcm3, T_K_ambient)['P_kbar']
1165
- # Now calculate density of H2O fluid
1166
-
1167
- # See https://www.youtube.com/watch?v=6wE4Tk6OjGY
1168
- Ptotal=P_CO2/(1-XH2O) # Calculate the total pressure from the pressure we know for CO2.
1169
- P_H2O=Ptotal*XH2O # Now calculate the pressure of H2O. You could also do this as PTot*XH2O
1170
-
1171
- # calculate density of H2O using EOS
1172
- H2O_dens=calculate_rho_for_P_T_H2O(P_kbar=P_H2O,T_K=T_K_ambient)
1173
-
1174
- # Calculate the bulk density by re-arranging the two volume equations
1175
- rho_orig_no_H_loss=(CO2_dens_gcm3*H2O_dens)/((1-XH2O_mass)*H2O_dens+XH2O_mass*CO2_dens_gcm3)
1176
-
1177
-
1178
-
1179
- if fast_calcs is True:
1180
- if Hloss is True:
1181
- P=calculate_Pressure_DZ2006(density=rho_orig_H_loss, T_K=T_K, XH2O=XH2O)
1182
- if Hloss is False:
1183
- P=calculate_Pressure_DZ2006(density=rho_orig_no_H_loss, T_K=T_K, XH2O=XH2O)
1184
- return P/1000
1185
-
1186
- else:
1187
-
1188
- # Lets calculate the pressure using SW96
1189
- P_SW=calculate_P_for_rho_T(T_K=T_K, CO2_dens_gcm3=rho_meas, EOS='SW96')
1190
- P_SP=calculate_P_for_rho_T(T_K=T_K, CO2_dens_gcm3=rho_meas, EOS='SP94')
1191
- # Run Duan and Zhang with no XHO@ to start with
1192
- P_DZ=calculate_Pressure_DZ2006(density=rho_meas, T_K=T_K, XH2O=XH2O*0)
1193
- # Now doing it with XH2O for two different densities
1194
-
1195
- P_DZ_mix_H_loss=calculate_Pressure_DZ2006(density=rho_orig_H_loss, T_K=T_K, XH2O=XH2O)
1196
- P_DZ_mix_noH_loss=calculate_Pressure_DZ2006(density=rho_orig_no_H_loss, T_K=T_K, XH2O=XH2O)
1197
-
1198
- df=pd.DataFrame(data={
1199
- 'P_kbar_pureCO2_SW96': P_SW['P_kbar'],
1200
- 'P_kbar_pureCO2_SP94': P_SP['P_kbar'],
1201
- 'P_kbar_pureCO2_DZ06': P_DZ/1000,
1202
- 'P_kbar_mixCO2_DZ06_Hloss': P_DZ_mix_H_loss/1000,
1203
- 'P_kbar_mixCO2_DZ06_no_Hloss': P_DZ_mix_noH_loss/1000,
1204
- 'P Mix_Hloss/P Pure DZ06': P_DZ_mix_H_loss/P_DZ,
1205
- 'P Mix_no_Hloss/P Pure DZ06': P_DZ_mix_noH_loss/P_DZ,
1206
- 'rho_mix_calc_Hloss': rho_orig_H_loss,
1207
- 'rho_mix_calc_noHloss': rho_orig_no_H_loss,
1208
- 'CO2_dens_gcm3': rho_meas,
1209
- 'T_K': T_K,
1210
- 'XH2O': XH2O})
1211
-
1212
- return df
1213
-
1214
-
1215
- def calculate_rho_for_P_T_H2O(P_kbar, T_K):
1216
- """ 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
1217
- doi:10.1063/1.1461829.
1218
-
1219
- Parameters
1220
- ---------------------
1221
- P_kbar: int, float, pd.Series, np.array
1222
- Pressure in kbar
1223
-
1224
- T_K: int, float, pd.Series, np.array
1225
- Temperature in Kelvin
1226
-
1227
- Returns
1228
- --------------------
1229
- pd.Series
1230
- H2O density in g/cm3
1231
-
1232
- """
1233
- if isinstance(P_kbar, pd.Series):
1234
- P_kbar=np.array(P_kbar)
1235
- if isinstance(T_K, pd.Series):
1236
- T_K=np.array(T_K)
1237
-
1238
- P_Pa=P_kbar*10**8
1239
-
1240
- try:
1241
- import CoolProp.CoolProp as cp
1242
- except ImportError:
1243
- 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')
1244
-
1245
- H2O_dens_gcm3=cp.PropsSI('D', 'P', P_Pa, 'T', T_K, 'H2O')/1000
1246
-
1247
- return pd.Series(H2O_dens_gcm3)
1248
-
1249
-
1250
-
1251
-
1252
-
1253
-
1254
-
1255
-