proteus 1.9.0
C/C++/Fortran libraries
Loading...
Searching...
No Matches
co2_brine_eos.h
Go to the documentation of this file.
1#ifndef CO2_BRINE_EOS_H
2#define CO2_BRINE_EOS_H
3// Spycher-Pruess-Ennis-King (2003) CO2-brine equilibrium EOS -- C++ port of
4// co2_brine_eos.py (P2). Header-only, no proteus/xtensor deps so it is
5// standalone-testable. All constants/equations match the numpy reference.
6//
7// Spycher, Pruess & Ennis-King (2003), GCA 67(16) 3015-3031 (LBNL-50991)
8//
9// Salt-free (m_NaCl wired but the SP2005 salting-out is a follow-up).
10#include <cmath>
11#include <algorithm>
12
13namespace m_comp_co2 {
14namespace eos {
15
16// --- SP2003 constants (verbatim, == co2_brine_eos.py) ----------------------
17constexpr double R_CM3_BAR = 83.1446; // cm^3 bar /(mol K)
18constexpr double P0_BAR = 1.0;
19constexpr double M_PER_KG_H2O = 55.508;
20
21constexpr double A_CO2_0 = 7.54e7; // a_CO2(T) = A_CO2_0 - A_CO2_1*T(K)
22constexpr double A_CO2_1 = 4.02e4;
23constexpr double A_H2O_CO2 = 7.89e7;
24constexpr double B_CO2 = 27.80;
25constexpr double B_H2O = 18.10;
26
27constexpr double V_H2O = 18.5, V_CO2 = 32.1; // partial molar volumes cm^3/mol
28
29// Brine compressibility: rho_a(p) = rho_a(T,X)*exp(C_F_BRINE*(p - P_REF_BRINE)),
30// p in Pa. Gives the aqueous phase a pressure dependence so the comp-0 pressure
31// equation is non-degenerate in single-phase brine.
32constexpr double C_F_BRINE = 4.5e-10; // isothermal compressibility [1/Pa]
33constexpr double P_REF_BRINE = 1.01325e5; // reference pressure [Pa]
34
35constexpr double M_CO2_KG = 0.04401; // molar mass [kg/mol]
36constexpr double M_H2O_KG = 0.018015; // molar mass [kg/mol]
37
38// log10(K0) = a + bT + cT^2 + dT^3 + eT^4 , T in CELSIUS
39constexpr double K0_H2O[5] = {-2.215, 3.162e-2, -1.294e-4, 4.187e-7, -7.331e-10};
40constexpr double K0_CO2_G[5] = { 1.188, 1.307e-2, -5.445e-5, 0.0, 0.0};
41constexpr double K0_CO2_L[5] = { 1.168, 1.361e-2, -5.135e-5, 0.0, 0.0};
42
43constexpr double TC_CO2_K = 304.1282, PC_CO2_BAR = 73.773;
44constexpr double SW_A[4] = {-7.0602087, 1.9391218, -1.6463597, -3.2995634};
45
46inline double poly_logK0(const double c[5], double T_C) {
47 return c[0] + T_C*(c[1] + T_C*(c[2] + T_C*(c[3] + T_C*c[4])));
48}
49
50inline double co2_saturation_pressure_bar(double T_C) {
51 double T_K = T_C + 273.15;
52 if (T_K >= TC_CO2_K) return INFINITY;
53 double tau = 1.0 - T_K/TC_CO2_K;
54 double s = SW_A[0]*tau + SW_A[1]*std::pow(tau,1.5)
55 + SW_A[2]*tau*tau + SW_A[3]*std::pow(tau,4.0);
56 return PC_CO2_BAR*std::exp((TC_CO2_K/T_K)*s);
57}
58
59inline bool co2_is_liquid(double T_C, double P_bar) {
60 if (T_C + 273.15 >= TC_CO2_K) return false;
61 return P_bar >= co2_saturation_pressure_bar(T_C);
62}
63
64// real roots of x^3 + b x^2 + c x + d = 0 ; returns count, fills r[3]
65inline int solve_cubic_real(double b, double c, double d, double r[3]) {
66 double p = c - b*b/3.0;
67 double q = 2.0*b*b*b/27.0 - b*c/3.0 + d;
68 double shift = -b/3.0;
69 double disc = q*q/4.0 + p*p*p/27.0;
70 if (disc > 1e-14) { // one real root
71 double s = std::sqrt(disc);
72 r[0] = std::cbrt(-q/2.0 + s) + std::cbrt(-q/2.0 - s) + shift;
73 return 1;
74 } else if (disc < -1e-14) { // three real roots
75 double m = 2.0*std::sqrt(-p/3.0);
76 double theta = std::acos(std::max(-1.0, std::min(1.0,
77 3.0*q/(p*m))))/3.0;
78 for (int k = 0; k < 3; ++k)
79 r[k] = m*std::cos(theta - 2.0*M_PI*k/3.0) + shift;
80 return 3;
81 } else { // (near) repeated
82 double u = std::cbrt(-q/2.0);
83 r[0] = 2.0*u + shift;
84 r[1] = -u + shift;
85 return 2;
86 }
87}
88
89// molar volume [cm^3/mol] of the CO2-rich phase (RK, infinite-dilution a,b=CO2)
90inline double rk_molar_volume(double T_C, double P_bar, bool liquid) {
91 double T_K = T_C + 273.15;
92 double a = A_CO2_0 - A_CO2_1*T_K;
93 double b = B_CO2;
94 double A = a*P_bar/(R_CM3_BAR*R_CM3_BAR*std::pow(T_K,2.5));
95 double B = b*P_bar/(R_CM3_BAR*T_K);
96 double roots[3];
97 int n = solve_cubic_real(-1.0, (A - B - B*B), -A*B, roots);
98 double Zsel = liquid ? INFINITY : -INFINITY;
99 bool found = false;
100 for (int i = 0; i < n; ++i) {
101 if (roots[i] <= B) continue; // physical: Z > B
102 if (liquid) { if (roots[i] < Zsel) { Zsel = roots[i]; found = true; } }
103 else { if (roots[i] > Zsel) { Zsel = roots[i]; found = true; } }
104 }
105 if (!found) { // fall back: any root
106 for (int i = 0; i < n; ++i)
107 if (liquid ? roots[i] < Zsel : roots[i] > Zsel) Zsel = roots[i];
108 }
109 return Zsel*R_CM3_BAR*T_K/P_bar;
110}
111
112inline double ln_phi(double T_C, double P_bar, double V,
113 double b_k, double sum_aik) {
114 double T_K = T_C + 273.15;
115 double a = A_CO2_0 - A_CO2_1*T_K;
116 double b = B_CO2;
117 double RT15 = R_CM3_BAR*std::pow(T_K,1.5);
118 double ln_ratio = std::log((V + b)/V);
119 return std::log(V/(V - b))
120 + b_k/(V - b)
121 - (2.0*sum_aik/(RT15*b))*ln_ratio
122 + (a*b_k/(RT15*b*b))*(ln_ratio - b/(V + b))
123 - std::log(P_bar*V/(R_CM3_BAR*T_K));
124}
125
126// mutual solubilities at (T_C [C], P_bar [bar]) -> x_CO2 (aqueous), y_H2O (gas)
127inline void spycher_pruess_solubility(double T_C, double P_bar, double /*m_NaCl*/,
128 double& x_CO2, double& y_H2O) {
129 bool liquid = co2_is_liquid(T_C, P_bar);
130 double T_K = T_C + 273.15;
131 double V = rk_molar_volume(T_C, P_bar, liquid);
132 double phi_CO2 = std::exp(ln_phi(T_C, P_bar, V, B_CO2, A_CO2_0 - A_CO2_1*T_K));
133 double phi_H2O = std::exp(ln_phi(T_C, P_bar, V, B_H2O, A_H2O_CO2));
134 double K0H = std::pow(10.0, poly_logK0(K0_H2O, T_C));
135 double K0C = std::pow(10.0, poly_logK0(liquid ? K0_CO2_L : K0_CO2_G, T_C));
136 double dP = P_bar - P0_BAR;
137 double A = (K0H/(phi_H2O*P_bar))*std::exp(dP*V_H2O/(R_CM3_BAR*T_K));
138 double B = (phi_CO2*P_bar/(M_PER_KG_H2O*K0C))*std::exp(-dP*V_CO2/(R_CM3_BAR*T_K));
139 y_H2O = (1.0 - B)/(1.0/A - B);
140 x_CO2 = B*(1.0 - y_H2O);
141}
142
143inline double co2_rich_molar_density(double T_C, double P_bar) {
144 return 1.0e6/rk_molar_volume(T_C, P_bar, co2_is_liquid(T_C, P_bar));
145}
146
147inline double pure_water_density_gcc(double T_C) { // Kell (1975), g/cm^3
148 double num = 999.83952 + 16.945176*T_C - 7.9870401e-3*T_C*T_C
149 - 46.170461e-6*T_C*T_C*T_C + 105.56302e-9*std::pow(T_C,4)
150 - 280.54253e-12*std::pow(T_C,5);
151 return num/(1.0 + 16.879850e-3*T_C)/1000.0;
152}
153
154inline double brine_molar_density(double T_C, double P_bar, double x_CO2) {
155 double Vphi = 37.51 - 9.585e-2*T_C + 8.740e-4*T_C*T_C - 5.044e-7*T_C*T_C*T_C;
156 double rho_w = pure_water_density_gcc(T_C);
157 double n_H2O = M_PER_KG_H2O;
158 double n_CO2 = x_CO2/(1.0 - x_CO2)*n_H2O;
159 double vol_cm3 = (n_H2O*18.015)/rho_w + n_CO2*Vphi;
160 double rho0 = ((n_H2O + n_CO2)/vol_cm3)*1.0e6;
161 return rho0*std::exp(C_F_BRINE*(P_bar*1.0e5 - P_REF_BRINE));
162}
163
164} // namespace eos
165} // namespace m_comp_co2
166#endif
Int n
Definition Headers.h:28
Double q
Definition Headers.h:81
Double r
Definition Headers.h:83
Double * B
Definition Headers.h:41
Double s
Definition Headers.h:84
Double u
Definition Headers.h:89
Int num
Definition Headers.h:32
#define c(i)
Definition jf.h:21
constexpr double A_CO2_1
double ln_phi(double T_C, double P_bar, double V, double b_k, double sum_aik)
constexpr double R_CM3_BAR
void spycher_pruess_solubility(double T_C, double P_bar, double, double &x_CO2, double &y_H2O)
double poly_logK0(const double c[5], double T_C)
constexpr double C_F_BRINE
double co2_saturation_pressure_bar(double T_C)
double rk_molar_volume(double T_C, double P_bar, bool liquid)
constexpr double B_H2O
constexpr double TC_CO2_K
double co2_rich_molar_density(double T_C, double P_bar)
int solve_cubic_real(double b, double c, double d, double r[3])
constexpr double V_H2O
bool co2_is_liquid(double T_C, double P_bar)
constexpr double P0_BAR
constexpr double M_H2O_KG
constexpr double V_CO2
constexpr double K0_CO2_L[5]
constexpr double SW_A[4]
double brine_molar_density(double T_C, double P_bar, double x_CO2)
constexpr double M_CO2_KG
constexpr double B_CO2
constexpr double M_PER_KG_H2O
double pure_water_density_gcc(double T_C)
constexpr double A_CO2_0
constexpr double K0_H2O[5]
constexpr double P_REF_BRINE
constexpr double PC_CO2_BAR
constexpr double A_H2O_CO2
constexpr double K0_CO2_G[5]