Skip to content

Commit 665c44c

Browse files
authored
Søreide-Whitson EOS (#25)
1 parent 70ee84a commit 665c44c

13 files changed

Lines changed: 353 additions & 93 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
*.jl.mem
44
/docs/build/
55
Manifest.toml
6+
*.json

docs/src/examples/advanced.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ If many flashes of the same mixture are to be performed at different conditions,
3131
m = SSIFlash()
3232
K = zeros(number_of_components(eos))
3333
S = flash_storage(eos, conditions, method = m)
34-
@allocated V = flash_2ph!(S, K, eos, conditions, method = m)
34+
@allocated flash_2ph!(S, K, eos, conditions, method = m)
3535
3636
# output
3737

src/MultiComponentFlash.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module MultiComponentFlash
44
# Constants.
55
const MINIMUM_COMPOSITION = 1e-10
66
const IDEAL_GAS_CONSTANT = 8.3144598
7+
@enum COMPONENT_ENUM COMPONENT_N2 COMPONENT_H2O COMPONENT_CO2 COMPONENT_H2S COMPONENT_OTHER_COMPONENT
8+
79
# Load types first
810
include("mixture_types.jl")
911
include("eos_types.jl")

src/eos.jl

Lines changed: 84 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ Return number of components for the underlying mixture of the EOS.
88
"""
99
number_of_components(e::AbstractEOS) = number_of_components(e.mixture)
1010

11+
forces_per_phase(eos::GenericCubicEOS) = false
12+
13+
function get_phase(cond)
14+
return get(cond, :phase, :unknown)::Symbol
15+
end
16+
17+
function set_phase(cond, phase::Symbol, throw::Bool = false)
18+
if throw && haskey(cond, :phase) && cond.phase != :unknown
19+
throw(ArgumentError("Phase state already set to $(cond.phase), cannot change to $phase."))
20+
end
21+
return (p = cond.p, T = cond.T, z = cond.z, phase = phase)
22+
end
1123

1224
function static_coefficients(::AbstractGeneralizedCubic)
1325
return (0.4274802327, 0.08664035, 0.0, 1.0)
@@ -23,67 +35,32 @@ function weight_ai(eos::GenericCubicEOS{T, R}, cond, i) where {T<:AbstractGenera
2335
return eos.ω_a*T_r^(-1/2)
2436
end
2537

26-
# PengRobinson specialization
27-
function static_coefficients(::AbstractPengRobinson)
28-
return (0.457235529, 0.077796074, 1.0 + sqrt(2), 1.0 - sqrt(2))
29-
end
30-
31-
function weight_ai(eos::GenericCubicEOS{T}, cond, i) where T<:AbstractPengRobinson
32-
mix = eos.mixture
33-
m = molecular_property(mix, i)
34-
a = acentric_factor(m)
35-
T_r = reduced_temperature(mix, cond, i)
36-
tmp = (1.0 + (0.37464 + 1.54226*a - 0.26992*a*a)*(1-T_r^0.5))
37-
return eos.ω_a*(tmp*tmp)
38-
end
38+
# Peng Robinson and variants
39+
include("eos/peng_robinson.jl")
40+
include("eos/peng_robinson_corrected.jl")
41+
include("eos/soreide_whitson.jl")
42+
# Soave Redlich-Kwong
43+
include("eos/soave_redlich_kwong.jl")
44+
# Zudkevitch-Joffe
45+
include("eos/zudkevitch_joffe.jl")
3946

40-
function weight_ai(eos::GenericCubicEOS{PengRobinsonCorrected}, cond, i)
41-
mix = eos.mixture
42-
m = molecular_property(mix, i)
43-
a = acentric_factor(m)
44-
T_r = reduced_temperature(mix, cond, i)
45-
aa = a*a
46-
if a > 0.49
47-
# Use alternate expression.
48-
D = (0.379642 + 1.48503*a - 0.164423*aa + 0.016666*a*aa)
49-
else
50-
# Use standard expression.
51-
D = (0.37464 + 1.54226*a - 0.26992*aa)
52-
end
53-
tmp = 1.0 + D*(1.0-T_r^0.5)
54-
return eos.ω_a*(tmp*tmp);
47+
# Generic part
48+
Base.@propagate_inbounds function binary_interaction(eos::AbstractEOS, i::Int, j::Int, cond)
49+
return binary_interaction(eos.mixture, i, j)
5550
end
5651

57-
# ZudkevitchJoffe
58-
function weight_ai(eos::GenericCubicEOS{ZudkevitchJoffe}, cond, i)
59-
zj = eos.type
60-
mix = eos.mixture
61-
T = cond.T
62-
T_r = reduced_temperature(mix, cond, i)
63-
return eos.ω_a*zj.F_a(T, i)*T_r^(-0.5)
52+
Base.@propagate_inbounds function binary_interaction(mixture::MultiComponentMixture{R}, i, j) where {R}
53+
return binary_interaction(mixture.binary_interaction, i, j)::R
6454
end
6555

66-
function weight_bi(eos::GenericCubicEOS{ZudkevitchJoffe}, cond, i)
67-
zj = eos.type
68-
T = cond.T
69-
return eos.ω_b*zj.F_b(T, i)
56+
function binary_interaction(::Nothing, i, j)
57+
return 0.0
7058
end
7159

72-
# SoaveRedlichKwong
73-
function weight_ai(eos::GenericCubicEOS{SoaveRedlichKwong}, cond, i)
74-
mix = eos.mixture
75-
m = molecular_property(mix, i)
76-
a = acentric_factor(m)
77-
T_r = reduced_temperature(mix, cond, i)
78-
return eos.ω_a*(1 + (0.48 + 1.574*a - 0.176*a^2)*(1-T_r^(1/2)))^2;
60+
Base.@propagate_inbounds function binary_interaction(B::AbstractMatrix, i, j)
61+
return B[i, j]
7962
end
8063

81-
# Generic part
82-
binary_interaction(eos::AbstractEOS, i, j) = binary_interaction(eos.mixture, i, j)
83-
binary_interaction(mixture::MultiComponentMixture{R}, i, j) where {R} = binary_interaction(mixture.binary_interaction, i, j)::R
84-
binary_interaction(::Nothing, i, j) = 0.0
85-
Base.@propagate_inbounds binary_interaction(B::AbstractMatrix, i, j) = B[i, j]
86-
8764
"""
8865
mixture_compressibility_factor(eos, cond, [forces, scalars, phase])
8966
@@ -93,21 +70,31 @@ provided if they are already known.
9370
The compressibility factor adjusts the ideal gas law to account for non-linear behavior: ``pV = nRTZ``
9471
"""
9572

96-
function mixture_compressibility_factor(eos::AbstractCubicEOS, cond,
97-
forces = force_coefficients(eos, cond),
98-
scalars = force_scalars(eos, cond, forces),
99-
phase = :unknown)
73+
function mixture_compressibility_factor(
74+
eos::AbstractCubicEOS,
75+
cond,
76+
forces = force_coefficients(eos, cond),
77+
scalars = force_scalars(eos, cond, forces),
78+
phase = missing
79+
)
80+
if !ismissing(phase)
81+
cond = set_phase(cond, phase)
82+
end
10083
poly = eos_polynomial(eos, forces, scalars)
10184
roots = solve_roots(eos, poly)
102-
r = pick_root(eos, roots, cond, forces, scalars, phase)
85+
r = pick_root(eos, roots, cond, forces, scalars)
10386
return r
10487
end
10588

10689
minimum_allowable_root(eos::AbstractCubicEOS, forces, scalars) = scalars.B
10790
minimum_allowable_root(eos, forces, scalars) = 1e-16
10891

109-
@inline pick_root(eos, roots::Real, cond, forces, scalars, phase = :unknown) = roots
110-
function pick_root(eos, roots, cond, forces, scalars, phase = :unknown)
92+
@inline function pick_root(eos, roots::Real, cond, forces, scalars)
93+
return roots
94+
end
95+
96+
function pick_root(eos, roots, cond, forces, scalars)
97+
phase = get_phase(cond)
11198
r_ϵ = minimum_allowable_root(eos, forces, scalars)
11299
max_r = maximum(roots)
113100
min_r = minimum((x) -> x > r_ϵ ? x : Inf, roots)
@@ -160,7 +147,23 @@ function force_coefficients(eos::AbstractCubicEOS, cond; static_size = false)
160147
B_i = zeros(eT, n)
161148
end
162149
coeff = (A_ij = A_ij, A_i = A_i, B_i = B_i)
163-
force_coefficients!(coeff, eos, cond)
150+
update_force_coefficients!(coeff, eos, cond)
151+
return coeff
152+
end
153+
154+
function get_force_coefficients(forces, eos, cond)
155+
if forces_per_phase(eos)
156+
phase = get_phase(cond)
157+
if phase == :liquid
158+
return forces.liquid
159+
elseif phase == :vapor
160+
return forces.vapor
161+
else
162+
error("Forces per phase are only supported for liquid and vapor phases, not $phase.")
163+
end
164+
else
165+
return forces
166+
end
164167
end
165168

166169
"""
@@ -170,10 +173,22 @@ In-place update of force coefficients.
170173
171174
See also [`force_coefficients`](@ref)
172175
"""
173-
function force_coefficients!(coeff, eos::AbstractCubicEOS, arg...)
174-
update_attractive_linear!(coeff.A_i, eos, arg...)
175-
update_attractive_quadratic!(coeff.A_ij, coeff.A_i, eos, arg...)
176-
update_repulsive!(coeff.B_i, eos, arg...)
176+
function force_coefficients!(coeff, eos::AbstractCubicEOS, cond)
177+
if forces_per_phase(eos)
178+
cond = set_phase(cond, :liquid)
179+
update_force_coefficients!(coeff.liquid, eos, cond)
180+
cond = set_phase(cond, :vapor)
181+
update_force_coefficients!(coeff.vapor, eos, cond)
182+
else
183+
update_force_coefficients!(coeff, eos, cond)
184+
end
185+
return coeff
186+
end
187+
188+
function update_force_coefficients!(coeff, eos::AbstractCubicEOS, cond)
189+
update_attractive_linear!(coeff.A_i, eos, cond)
190+
update_attractive_quadratic!(coeff.A_ij, coeff.A_i, eos, cond)
191+
update_repulsive!(coeff.B_i, eos, cond)
177192
return coeff
178193
end
179194

@@ -201,7 +216,7 @@ function update_attractive_quadratic!(A_ij, A_i, eos::AbstractCubicEOS, cond)
201216
T = eltype(A_i)
202217
for i = 1:N
203218
@inbounds for j = i:N
204-
a = sqrt(A_i[i]*A_i[j])*(one(T) - binary_interaction(eos, i, j))
219+
a = sqrt(A_i[i]*A_i[j])*(one(T) - binary_interaction(eos, i, j, cond))
205220
a::T
206221
A_ij[i, j] = a
207222
A_ij[j, i] = a
@@ -227,7 +242,8 @@ end
227242
Compute EOS specific scalars for the current conditions based on the forces.
228243
"""
229244
function force_scalars(eos::AbstractCubicEOS, cond, forces)
230-
return cubic_scalars(forces.A_ij, forces.B_i, cond.z)
245+
f = get_force_coefficients(forces, eos, cond)
246+
return cubic_scalars(f.A_ij, f.B_i, cond.z)
231247
end
232248

233249
function cubic_scalars(A_ij, Bv, z)
@@ -279,7 +295,8 @@ Base.@propagate_inbounds function component_fugacity_coefficient(eos::AbstractCu
279295
# NOTE: This returns ln(ψ), not ψ!
280296
m1 = eos.m_1
281297
m2 = eos.m_2
282-
return component_fugacity_coefficient_cubic(m1, m2, cond.z, Z, scalars.A, scalars.B, forces.A_ij, forces.B_i, i)
298+
f = get_force_coefficients(forces, eos, cond)
299+
return component_fugacity_coefficient_cubic(m1, m2, cond.z, Z, scalars.A, scalars.B, f.A_ij, f.B_i, i)
283300
end
284301

285302
Base.@propagate_inbounds function component_fugacity_coefficient_cubic(m1, m2, x, Z, A, B, A_mat, B_i, i)

src/eos/peng_robinson.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
# PengRobinson specialization
3+
function static_coefficients(::AbstractPengRobinson)
4+
return (0.457235529, 0.077796074, 1.0 + sqrt(2), 1.0 - sqrt(2))
5+
end
6+
7+
function weight_ai(eos::GenericCubicEOS{T}, cond, i) where T<:AbstractPengRobinson
8+
mix = eos.mixture
9+
m = molecular_property(mix, i)
10+
a = acentric_factor(m)
11+
T_r = reduced_temperature(mix, cond, i)
12+
α_half = (1.0 + (0.37464 + 1.54226*a - 0.26992*a*a)*(1-T_r^0.5))
13+
return eos.ω_a*(α_half*α_half)
14+
end

src/eos/peng_robinson_corrected.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
function weight_ai(eos::GenericCubicEOS{PengRobinsonCorrected}, cond, i)
3+
mix = eos.mixture
4+
m = molecular_property(mix, i)
5+
a = acentric_factor(m)
6+
T_r = reduced_temperature(mix, cond, i)
7+
aa = a*a
8+
if a > 0.49
9+
# Use alternate expression.
10+
D = (0.379642 + 1.48503*a - 0.164423*aa + 0.016666*a*aa)
11+
else
12+
# Use standard expression.
13+
D = (0.37464 + 1.54226*a - 0.26992*aa)
14+
end
15+
tmp = 1.0 + D*(1.0-T_r^0.5)
16+
return eos.ω_a*(tmp*tmp);
17+
end

src/eos/soave_redlich_kwong.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SoaveRedlichKwong
2+
function weight_ai(eos::GenericCubicEOS{SoaveRedlichKwong}, cond, i)
3+
mix = eos.mixture
4+
m = molecular_property(mix, i)
5+
a = acentric_factor(m)
6+
T_r = reduced_temperature(mix, cond, i)
7+
return eos.ω_a*(1 + (0.48 + 1.574*a - 0.176*a^2)*(1-T_r^(1/2)))^2;
8+
end

src/eos/soreide_whitson.jl

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
forces_per_phase(eos::GenericCubicEOS{T}) where T<:SoreideWhitson = true
2+
3+
# Søreide-Whitson Peng-Robinson variant
4+
function weight_ai(eos::GenericCubicEOS{T}, cond, i) where T<:SoreideWhitson
5+
sw = eos.type
6+
mix = eos.mixture
7+
m = molecular_property(mix, i)
8+
a = acentric_factor(m)
9+
T_r = reduced_temperature(mix, cond, i)
10+
if sw.component_types[i] == COMPONENT_H2O
11+
# Use the water-specific expression.
12+
w1, w2, w3 = sw.water_coefficients
13+
α_half = 1.0 + w1*(1.0 - w2*T_r) + w3*(T_r^(-3)-1.0)
14+
else
15+
α_half = 1.0 + (0.37464 + 1.54226*a - 0.26992*a*a)*(1-T_r^0.5)
16+
end
17+
return eos.ω_a*(α_half*α_half)
18+
end
19+
20+
function binary_interaction(eos::GenericCubicEOS{T}, i::Int, j::Int, cond) where T<:SoreideWhitson
21+
if i == j
22+
bic = 0.0
23+
else
24+
phase = get_phase(cond)
25+
sw = eos.type
26+
if phase == :liquid
27+
cat_i = eos.type.component_types[i]
28+
cat_j = eos.type.component_types[j]
29+
# Use eq 12 from paper
30+
i_is_h2o = cat_i == COMPONENT_H2O
31+
j_is_h2o = cat_j == COMPONENT_H2O
32+
# in paper: i is hc, j is h2o
33+
if i_is_h2o || j_is_h2o
34+
# Special cases: H2O-H2S, H2O-N2 and H2O-CO2
35+
if i_is_h2o
36+
cat_other = cat_j
37+
ix = j
38+
else
39+
cat_other = cat_i
40+
ix = i
41+
end
42+
hc_property = molecular_property(eos.mixture, ix)
43+
acf = acentric_factor(hc_property)
44+
T_r = cond.T/critical_temperature(hc_property)
45+
if cat_other == COMPONENT_CO2
46+
bic = soreide_whitson_co2_aqueous_bic(sw, acf, T_r)
47+
elseif cat_other == COMPONENT_H2S
48+
bic = soreide_whitson_h2s_aqueous_bic(sw, acf, T_r)
49+
elseif cat_other == COMPONENT_N2
50+
bic = soreide_whitson_n2_aqueous_bic(sw, acf, T_r)
51+
else
52+
bic = soreide_whitson_hc_aqueous_bic(sw, acf, T_r)
53+
end
54+
else
55+
# Use default.
56+
bic = binary_interaction(eos.mixture, i, j)
57+
end
58+
elseif phase == :vapor
59+
bic = binary_interaction(eos.mixture, i, j)
60+
else
61+
error("Søreide-Whitson requires the phase state to be specified for binary interactions (was: $phase).")
62+
end
63+
end
64+
return bic
65+
end
66+
67+
function soreide_whitson_n2_aqueous_bic(sw::SoreideWhitson, acf_i, T_ri)
68+
c_sw = sw.molality
69+
return -1.70235*(1 + 0.025587*c_sw^0.75) + 0.44338*(1 + 0.08126*c_sw^0.75)*T_ri
70+
end
71+
72+
function soreide_whitson_h2s_aqueous_bic(sw::SoreideWhitson, acf_i, T_ri)
73+
return -0.20441 + 0.234267*T_ri
74+
end
75+
76+
function soreide_whitson_co2_aqueous_bic(sw::SoreideWhitson, acf_i, T_ri)
77+
c_sw = sw.molality
78+
return -0.31092*(1 + 0.15587*c_sw^0.7505) + 0.23580 * (1 + 0.17837*c_sw^0.979)*T_ri - 21.2566*exp(-6.7222*T_ri- c_sw)
79+
end
80+
81+
function soreide_whitson_hc_aqueous_bic(sw::SoreideWhitson, acf_i, T_ri)
82+
c_sw = sw.molality
83+
a_0, a_1, a_2 = sw.A
84+
a_mw_0, a_mw_1, a_mw_2 = sw.A_mw
85+
α_0, α_1, α_2 = sw.alphas
86+
87+
A_0 = a_0 + a_mw_0*sign(acf_i)*abs(acf_i)^(-0.1)
88+
A_1 = a_1 + a_mw_1*acf_i
89+
A_2 = a_2 + a_mw_2*acf_i
90+
return A_0*(1 + α_0*c_sw) + A_1*T_ri*(1 + α_1*c_sw) + A_2*T_ri*(1 + α_2*c_sw)
91+
end

src/eos/zudkevitch_joffe.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# ZudkevitchJoffe
2+
function weight_ai(eos::GenericCubicEOS{ZudkevitchJoffe}, cond, i)
3+
zj = eos.type
4+
mix = eos.mixture
5+
T = cond.T
6+
T_r = reduced_temperature(mix, cond, i)
7+
return eos.ω_a*zj.F_a(T, i)*T_r^(-0.5)
8+
end
9+
10+
function weight_bi(eos::GenericCubicEOS{ZudkevitchJoffe}, cond, i)
11+
zj = eos.type
12+
T = cond.T
13+
return eos.ω_b*zj.F_b(T, i)
14+
end

0 commit comments

Comments
 (0)