Skip to content

Commit 7a23d8a

Browse files
Copilotmoyner
andauthored
Add PVDO table and PVTTableSet return type to PVTExperiments (#28)
* Initial plan * Add PVDO table and PVTTableSet return type to PVTExperiments Co-authored-by: moyner <454871+moyner@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: moyner <454871+moyner@users.noreply.github.com>
1 parent 3b0e06b commit 7a23d8a

6 files changed

Lines changed: 164 additions & 13 deletions

File tree

src/PVTExperiments/PVTExperiments.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ module PVTExperiments
2121
include("tables.jl")
2222
include("interface.jl")
2323

24-
export generate_pvt_tables
24+
export generate_pvt_tables, PVTTableSet
2525
end

src/PVTExperiments/interface.jl

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ High-level interface that generates complete black oil PVT tables from a
55
compositional fluid description.
66
77
Goes from a fluid sample, reservoir temperature and surface conditions to
8-
PVTO/PVDG (or PVTG/PVDG) tables + surface densities.
8+
PVTO/PVDG/PVDO (or PVTG/PVDG/PVDO) tables + surface densities.
99
1010
# Arguments
1111
- `eos`: Equation of state (e.g., `GenericCubicEOS(mixture, PengRobinson())`)
@@ -25,13 +25,15 @@ PVTO/PVDG (or PVTG/PVDG) tables + surface densities.
2525
- `n_pvto`: Number of Rs levels for PVTO. Default: 15
2626
- `n_pvtg`: Number of pressure levels for PVTG. Default: 15
2727
- `n_pvdg`: Number of pressure points for PVDG. Default: 20
28+
- `n_pvdo`: Number of pressure points for PVDO. Default: 20
2829
- `n_undersaturated`: Undersaturated points per level. Default: 5
2930
3031
# Returns
31-
A NamedTuple with fields:
32+
A `PVTTableSet` instance with fields:
3233
- `pvto`: PVTOTable or `nothing`
3334
- `pvtg`: PVTGTable or `nothing`
3435
- `pvdg`: PVDGTable
36+
- `pvdo`: PVDOTable
3537
- `surface_densities`: SurfaceDensities
3638
- `saturation_pressure`: Saturation pressure (Pa)
3739
- `is_bubblepoint`: Whether the fluid has a bubble point (oil) or dew point (gas)
@@ -46,6 +48,7 @@ function generate_pvt_tables(eos, z, T_res;
4648
n_pvto = 15,
4749
n_pvtg = 15,
4850
n_pvdg = 20,
51+
n_pvdo = 20,
4952
n_undersaturated = 5
5053
)
5154
z = collect(Float64, z)
@@ -113,19 +116,38 @@ function generate_pvt_tables(eos, z, T_res;
113116
T_sc = T_sc
114117
)
115118

119+
# PVDO is always generated
120+
# For oil systems, use the overall composition
121+
# For gas systems, use the oil composition from the first condensation step
122+
if is_bp
123+
z_oil_dead = z
124+
else
125+
# Get oil composition near dew point
126+
props_dp = flash_and_properties(eos, p_sat * 0.95, T_res, z)
127+
z_oil_dead = props_dp.x
128+
end
129+
130+
pvdo_result = pvdo_table(eos, z_oil_dead, T_res;
131+
p_range = p_range,
132+
n_points = n_pvdo,
133+
p_sc = p_sc,
134+
T_sc = T_sc
135+
)
136+
116137
# Surface densities
117138
sd = surface_densities(eos, z, T_res;
118139
p_sc = p_sc,
119140
T_sc = T_sc,
120141
separator_stages = separator_stages
121142
)
122143

123-
return (
124-
pvto = pvto_result,
125-
pvtg = pvtg_result,
126-
pvdg = pvdg_result,
127-
surface_densities = sd,
128-
saturation_pressure = p_sat,
129-
is_bubblepoint = is_bp
144+
return PVTTableSet(
145+
pvto_result,
146+
pvtg_result,
147+
pvdg_result,
148+
pvdo_result,
149+
sd,
150+
p_sat,
151+
is_bp
130152
)
131153
end

src/PVTExperiments/tables.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,47 @@ function pvtg_table(eos, z, T;
313313
return PVTGTable(p_values, Rv_sat_values, Rv_sub_table, Bg_table, mu_g_table)
314314
end
315315

316+
"""
317+
pvdo_table(eos, z, T; p_range, n_points, p_sc, T_sc)
318+
319+
Generate a PVDO (dead oil) table.
320+
321+
# Arguments
322+
- `eos`: Equation of state
323+
- `z`: Oil composition (mole fractions)
324+
- `T`: Temperature (K)
325+
- `p_range`: Pressure range. Default: (50e6, 1e5)
326+
- `n_points`: Number of pressure points. Default: 20
327+
- `p_sc`: Standard condition pressure (Pa). Default: 101325.0
328+
- `T_sc`: Standard condition temperature (K). Default: 288.706
329+
"""
330+
function pvdo_table(eos, z, T;
331+
p_range = (50e6, 1e5),
332+
n_points = 20,
333+
p_sc = 101325.0,
334+
T_sc = 288.706
335+
)
336+
z = collect(Float64, z)
337+
z ./= sum(z)
338+
339+
pressures = collect(range(p_range[2], p_range[1], length = n_points))
340+
sort!(pressures)
341+
342+
Bo_arr = zeros(n_points)
343+
mu_o_arr = zeros(n_points)
344+
345+
props_sc = flash_and_properties(eos, p_sc, T_sc, z)
346+
V_mol_sc = props_sc.V_mol_l
347+
348+
for (i, p) in enumerate(pressures)
349+
props = flash_and_properties(eos, p, T, z)
350+
Bo_arr[i] = props.V_mol_l / V_mol_sc
351+
mu_o_arr[i] = props.μ_l
352+
end
353+
354+
return PVDOTable(pressures, Bo_arr, mu_o_arr)
355+
end
356+
316357
"""
317358
surface_densities(eos, z, T; p_sc, T_sc, separator_stages)
318359

src/PVTExperiments/types.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,22 @@ struct PVTGTable
198198
mu_g::Vector{Vector{Float64}}
199199
end
200200

201+
"""
202+
PVDOTable
203+
204+
Black oil PVDO (dead oil) table.
205+
206+
# Fields
207+
- `p`: Pressure values (Pa)
208+
- `Bo`: Oil formation volume factor (m³/m³)
209+
- `mu_o`: Oil viscosity (Pa·s)
210+
"""
211+
struct PVDOTable
212+
p::Vector{Float64}
213+
Bo::Vector{Float64}
214+
mu_o::Vector{Float64}
215+
end
216+
201217
"""
202218
SurfaceDensities
203219
@@ -211,3 +227,27 @@ struct SurfaceDensities
211227
oil::Float64
212228
gas::Float64
213229
end
230+
231+
"""
232+
PVTTableSet
233+
234+
Collection of black oil PVT tables generated from a compositional fluid description.
235+
236+
# Fields
237+
- `pvto`: Live oil table (PVTO) or `nothing` if not applicable
238+
- `pvtg`: Wet gas table (PVTG) or `nothing` if not applicable
239+
- `pvdg`: Dry gas table (PVDG)
240+
- `pvdo`: Dead oil table (PVDO)
241+
- `surface_densities`: Oil and gas densities at standard conditions
242+
- `saturation_pressure`: Bubble or dew point pressure (Pa)
243+
- `is_bubblepoint`: `true` if the fluid has a bubble point (oil), `false` if dew point (gas)
244+
"""
245+
struct PVTTableSet
246+
pvto::Union{PVTOTable, Nothing}
247+
pvtg::Union{PVTGTable, Nothing}
248+
pvdg::PVDGTable
249+
pvdo::PVDOTable
250+
surface_densities::SurfaceDensities
251+
saturation_pressure::Float64
252+
is_bubblepoint::Bool
253+
end

src/PVTExperiments/utils.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,27 @@ function Base.show(io::IO, s::SurfaceDensities)
328328
@printf(io, " Oil: %.2f kg/m³\n", s.oil)
329329
@printf(io, " Gas: %.4f kg/m³\n", s.gas)
330330
end
331+
332+
function Base.show(io::IO, t::PVDOTable)
333+
println(io, "PVDO Table")
334+
println(io, "=" ^ 60)
335+
@printf(io, "%14s %12s %12s\n", "P (Pa)", "Bo", "μ_o (Pa·s)")
336+
println(io, "-" ^ 60)
337+
for i in eachindex(t.p)
338+
@printf(io, "%14.4e %12.6f %12.4e\n", t.p[i], t.Bo[i], t.mu_o[i])
339+
end
340+
end
341+
342+
function Base.show(io::IO, s::PVTTableSet)
343+
println(io, "PVTTableSet")
344+
println(io, "=" ^ 60)
345+
bp_str = s.is_bubblepoint ? "bubble point (oil)" : "dew point (gas)"
346+
@printf(io, "Saturation pressure: %.4e Pa (%.2f bar) [%s]\n",
347+
s.saturation_pressure, s.saturation_pressure / 1e5, bp_str)
348+
println(io, "-" ^ 60)
349+
println(io, " pvto: ", isnothing(s.pvto) ? "nothing" : "PVTOTable ($(length(s.pvto.Rs)) Rs levels)")
350+
println(io, " pvtg: ", isnothing(s.pvtg) ? "nothing" : "PVTGTable ($(length(s.pvtg.p)) pressure levels)")
351+
println(io, " pvdg: PVDGTable ($(length(s.pvdg.p)) pressure points)")
352+
println(io, " pvdo: PVDOTable ($(length(s.pvdo.p)) pressure points)")
353+
show(io, s.surface_densities)
354+
end

test/pvt_experiments.jl

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,19 @@ import MultiComponentFlash.PVTExperiments as PVTExp
147147
@test contains(output, "PVDG")
148148
end
149149

150+
@testset "PVDO Table" begin
151+
table = PVTExp.pvdo_table(eos, z_oil, T_res; n_points = 10)
152+
@test table isa PVTExp.PVDOTable
153+
@test length(table.p) == 10
154+
@test all(table.Bo .> 0)
155+
@test all(table.mu_o .> 0)
156+
# Test printing
157+
io = IOBuffer()
158+
show(io, table)
159+
output = String(take!(io))
160+
@test contains(output, "PVDO")
161+
end
162+
150163
@testset "PVTG Table" begin
151164
table = PVTExp.pvtg_table(eos, z_gas, T_res; n_rv = 5, n_undersaturated = 2)
152165
@test table isa PVTExp.PVTGTable
@@ -178,18 +191,27 @@ import MultiComponentFlash.PVTExperiments as PVTExp
178191

179192
@testset "High-Level Interface - Oil" begin
180193
tables = generate_pvt_tables(eos, z_oil, T_res;
181-
n_pvto = 5, n_pvdg = 10, n_undersaturated = 2)
194+
n_pvto = 5, n_pvdg = 10, n_pvdo = 10, n_undersaturated = 2)
195+
@test tables isa PVTExp.PVTTableSet
182196
@test tables.pvto !== nothing
183197
@test tables.pvdg !== nothing
198+
@test tables.pvdo !== nothing
184199
@test tables.surface_densities isa PVTExp.SurfaceDensities
185200
@test tables.saturation_pressure > 0
186201
@test tables.is_bubblepoint == true
202+
# Test printing
203+
io = IOBuffer()
204+
show(io, tables)
205+
output = String(take!(io))
206+
@test contains(output, "PVTTableSet")
187207
end
188208

189209
@testset "High-Level Interface - Gas" begin
190210
tables = generate_pvt_tables(eos, z_gas, T_res;
191-
n_pvtg = 5, n_pvdg = 10)
211+
n_pvtg = 5, n_pvdg = 10, n_pvdo = 10)
212+
@test tables isa PVTExp.PVTTableSet
192213
@test tables.pvdg !== nothing
214+
@test tables.pvdo !== nothing
193215
@test tables.surface_densities isa PVTExp.SurfaceDensities
194216
@test tables.saturation_pressure > 0
195217
end
@@ -201,9 +223,11 @@ import MultiComponentFlash.PVTExperiments as PVTExp
201223
]
202224
tables = generate_pvt_tables(eos, z_oil, T_res;
203225
separator_stages = stages,
204-
n_pvto = 5, n_pvdg = 10, n_undersaturated = 2)
226+
n_pvto = 5, n_pvdg = 10, n_pvdo = 10, n_undersaturated = 2)
227+
@test tables isa PVTExp.PVTTableSet
205228
@test tables.pvto !== nothing
206229
@test tables.pvdg !== nothing
230+
@test tables.pvdo !== nothing
207231
@test tables.surface_densities isa PVTExp.SurfaceDensities
208232
end
209233
end

0 commit comments

Comments
 (0)