From b798e1b08a20016bd5a805b146e77683b009657a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 8 Feb 2024 13:01:08 +0100 Subject: [PATCH 01/92] Improve blackoil initialization --- src/blackoil/blackoil.jl | 21 ++++++++++++++++----- src/blackoil/variables/variables.jl | 8 ++++++++ src/utils.jl | 4 ++-- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/blackoil/blackoil.jl b/src/blackoil/blackoil.jl index ed9c8123..40955713 100644 --- a/src/blackoil/blackoil.jl +++ b/src/blackoil/blackoil.jl @@ -105,7 +105,7 @@ function cnv_mb_errors_bo(r, Φ, b, dt, rhoS, ::Val{N}) where N return (Tuple(cnv), Tuple(mb)) end -function handle_alternate_primary_variable_spec!(init, found, sys::StandardBlackOilSystem) +function handle_alternate_primary_variable_spec!(init, found, rmodel, sys::StandardBlackOilSystem) # Internal utility to handle non-trivial specification of primary variables nph = number_of_phases(sys) @assert haskey(init, :Pressure) @@ -137,18 +137,29 @@ function handle_alternate_primary_variable_spec!(init, found, sys::StandardBlack F_rv = sys.rv_max if has_disgas(sys) rs = init[:Rs] + rs_var = rmodel[:Rs] else rs = zeros(nc) + rs_var = nothing end if has_vapoil(sys) - rv = init[:Rs] + rv = init[:Rv] + rv_var = rmodel[:Rv] else rv = zeros(nc) + rv_var = nothing end so = @. 1.0 - sw - sg - bo = map( - (w, o, g, r, v, p) -> blackoil_unknown_init(F_rs, F_rv, w, o, g, r, v, p), - sw, so, sg, rs, rv, pressure) + bo = Vector{BlackOilX{Float64}}() + sizehint!(bo, nc) + for i in 1:nc + reg_rs = region(rs_var, i) + reg_rv = region(rv_var, i) + F_rs_i = table_by_region(F_rs, reg_rs) + F_rv_i = table_by_region(F_rv, reg_rv) + v = blackoil_unknown_init(F_rs_i, F_rv_i, sw[i], so[i], sg[i], rs[i], rv[i], pressure[i]) + push!(bo, v) + end init[:BlackOilUnknown] = bo push!(found, :BlackOilUnknown) end diff --git a/src/blackoil/variables/variables.jl b/src/blackoil/variables/variables.jl index 36bb4752..20cdfbca 100644 --- a/src/blackoil/variables/variables.jl +++ b/src/blackoil/variables/variables.jl @@ -24,6 +24,10 @@ struct Rs{F, R} <: ScalarVariable regions::R end +@inline function region(pv::Rs, cell) + return region(pv.regions, cell) +end + function Rs(rs_max::F; regions::R = nothing) where {F, R} return Rs{F, R}(rs_max, regions) end @@ -49,6 +53,10 @@ function Rv(rv_max::F; regions::R = nothing) where {F, R} return Rv{F, R}(rv_max, regions) end +@inline function region(pv::Rv, cell) + return region(pv.regions, cell) +end + function Jutul.subvariable(v::Rv, map::FiniteVolumeGlobalMap) c = map.cells regions = Jutul.partition_variable_slice(v.regions, c) diff --git a/src/utils.jl b/src/utils.jl index 0003cad3..583e6220 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -704,7 +704,7 @@ function setup_reservoir_state(rmodel::SimulationModel; kwarg...) end res_init[k] = v end - handle_alternate_primary_variable_spec!(res_init, found, rmodel.system) + handle_alternate_primary_variable_spec!(res_init, found, rmodel, rmodel.system) if length(found) != length(pvars) missing_primary_variables = setdiff(pvars, found) @warn "Not all primary variables were initialized for reservoir model." missing_primary_variables @@ -712,7 +712,7 @@ function setup_reservoir_state(rmodel::SimulationModel; kwarg...) return setup_state(rmodel, res_init) end -function handle_alternate_primary_variable_spec!(res_init, found, system) +function handle_alternate_primary_variable_spec!(res_init, found, rmodel, system) # Internal utility to handle non-trivial specification of primary variables return res_init end From 49485f2a6a4040dcfb34fdaa23cfe7022f09c474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 8 Feb 2024 13:01:22 +0100 Subject: [PATCH 02/92] Add verbose option to setup_case_from_parsed_data --- src/input_simulation/data_input.jl | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index e2d6fdbe..5d46638b 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -50,16 +50,24 @@ function setup_case_from_data_file( return out end -function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans = true, kwarg...) +function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans = true, verbose = false, kwarg...) + function msg(s) + if verbose + jutul_message("Setup", s) + end + end + msg("Parsing physics and system.") sys, pvt = parse_physics_types(datafile, pvt_region = 1) is_blackoil = sys isa StandardBlackOilSystem is_compositional = sys isa CompositionalSystem + msg("Parsing reservoir domain.") domain = parse_reservoir(datafile) pvt_reg = reservoir_regions(domain, :pvtnum) has_pvt = isnothing(pvt_reg) # Parse wells + msg("Parsing schedule.") wells, controls, limits, cstep, dt, well_forces = parse_schedule(domain, sys, datafile; simple_well = simple_well) - + msg("Setting up model with $(length(wells)) wells.") wells_pvt = Dict() wells_systems = [] for w in wells @@ -105,6 +113,7 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans end end end + msg("Setting up parameters.") parameters = setup_parameters(model) if haskey(datafile["PROPS"], "SWL") G = physical_representation(domain) @@ -114,8 +123,11 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans if use_ijk_trans parameters[:Reservoir][:Transmissibilities] = reservoir_transmissibility(domain, version = :ijk); end + msg("Setting up forces.") forces = parse_forces(model, wells, controls, limits, cstep, dt, well_forces) + msg("Setting up initial state.") state0 = parse_state0(model, datafile) + msg("Setup complete.") return JutulCase(model, dt, forces, state0 = state0, parameters = parameters) end @@ -1187,6 +1199,7 @@ function well_completion_sortperm(domain, wspec, order_t0, wc, dir) start = wspec.head use_dir = true while length(wc) > 0 + @info "Well..." wc closest_ix = 0 closest_xyz_distance = Inf if use_dir From 974c6ade7cdda4207eaeb13099696452d7dc3782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 8 Feb 2024 13:10:37 +0100 Subject: [PATCH 03/92] Speed up initial state by explicit loop --- src/init/init.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 8dfb8029..e2e73302 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -196,12 +196,15 @@ function parse_state0_equil(model, datafile) eq = equil[ereg] for sreg in 1:nsat for preg in 1:npvt - cells = findall( - i ->satnum[i] == sreg && + cells = Int[] + sizehint!(cells, ncells÷(nsat*npvt*nequil)) + for i in 1:ncells + if satnum[i] == sreg && pvtnum[i] == preg && - eqlnum[i] == ereg, - 1:ncells - ) + eqlnum[i] == ereg + push!(cells, i) + end + end ncells_reg = length(cells) if ncells_reg == 0 From da500ba429243d853ac8c3b24b670a73375a3b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 8 Feb 2024 13:52:15 +0100 Subject: [PATCH 04/92] Speed up initialization with many regions --- src/init/init.jl | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index e2e73302..7f7521ae 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -194,18 +194,13 @@ function parse_state0_equil(model, datafile) inits_cells = [] for ereg in 1:nequil eq = equil[ereg] + cells_eqlnum = findall(isequal(ereg), eqlnum) for sreg in 1:nsat + cells_satnum = findall(isequal(sreg), satnum) + cells_sat_and_pvt = intersect_sorted(cells_satnum, cells_eqlnum) for preg in 1:npvt - cells = Int[] - sizehint!(cells, ncells÷(nsat*npvt*nequil)) - for i in 1:ncells - if satnum[i] == sreg && - pvtnum[i] == preg && - eqlnum[i] == ereg - push!(cells, i) - end - end - + cells_pvtnum = findall(isequal(preg), pvtnum) + cells = intersect_sorted(cells_pvtnum, cells_sat_and_pvt) ncells_reg = length(cells) if ncells_reg == 0 continue @@ -360,6 +355,27 @@ function parse_state0_equil(model, datafile) return init end +function intersect_sorted(a::Vector{T}, b::Vector{T}) where T + na = length(a) + nb = length(b) + c = Vector{T}() + ptr_a = ptr_b = 1 + while ptr_a <= na && ptr_b <= nb + a_val = a[ptr_a] + b_val = b[ptr_b] + if a_val == b_val + push!(c, a_val) + ptr_a += 1 + ptr_b += 1 + elseif a_val < b_val + ptr_a += 1 + else + ptr_b += 1 + end + end + return c +end + function fill_subinit!(x::Vector, cells, v::Vector) @assert length(v) == length(cells) for (i, c) in enumerate(cells) From 7d50304dfae155ef34a4cedba2982e826bda9aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 9 Feb 2024 14:11:00 +0100 Subject: [PATCH 05/92] Remove debug output. --- src/input_simulation/data_input.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 5d46638b..3c9d0f36 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -1199,7 +1199,6 @@ function well_completion_sortperm(domain, wspec, order_t0, wc, dir) start = wspec.head use_dir = true while length(wc) > 0 - @info "Well..." wc closest_ix = 0 closest_xyz_distance = Inf if use_dir From 93f8636a6ae3213ab67622a2576600905d67ee21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 10 Feb 2024 21:16:41 +0100 Subject: [PATCH 06/92] Clamp vapor fraction for k value flash --- src/multicomponent/variables/flash.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multicomponent/variables/flash.jl b/src/multicomponent/variables/flash.jl index 0cc929b6..dd2f8f0a 100644 --- a/src/multicomponent/variables/flash.jl +++ b/src/multicomponent/variables/flash.jl @@ -276,7 +276,7 @@ function k_value_flash!(result::FR, eos, P, T, Z, z) where FR end @. x = Z @. y = Z - V = convert(Num_t, V) + V = convert(Num_t, clamp(V, 0.0, 1.0)) else phase_state = MultiComponentFlash.two_phase_lv V = add_derivatives_to_vapor_fraction_rachford_rice(V, K_ad, Z, K, z) From 5c5e1b617484a6f91771dea91e5f58ea1de69fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 10 Feb 2024 21:16:51 +0100 Subject: [PATCH 07/92] Improve assert message --- src/multicomponent/wells.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multicomponent/wells.jl b/src/multicomponent/wells.jl index c2debb09..4b62c284 100644 --- a/src/multicomponent/wells.jl +++ b/src/multicomponent/wells.jl @@ -254,6 +254,6 @@ function compositional_surface_densities(state, system, S_l::S_T, S_v::S_T, rho_ rhoS[v] = rho_v volume[l] = S_l*rem volume[v] = S_v*rem - @assert sum(volume) ≈ 1.0 + @assert sum(volume) ≈ 1.0 "Volume should sum to 1, was $(sum(volume))" return (rhoS, volume) end From a2996d450533f3745226bbd275faaaa42eb733c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 12 Feb 2024 15:36:42 +0100 Subject: [PATCH 08/92] Add two variable defs for CO2 models --- src/multicomponent/variables/density.jl | 39 +++++++++++++++++++++ src/multicomponent/variables/saturations.jl | 38 ++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/multicomponent/variables/density.jl b/src/multicomponent/variables/density.jl index a3b89197..c7402362 100644 --- a/src/multicomponent/variables/density.jl +++ b/src/multicomponent/variables/density.jl @@ -32,3 +32,42 @@ end rho[a, i] = rhos[a]*shrinkage(pvt, p) end end + +struct BrineCO2MixingDensities{T} <: AbstractCompositionalDensities + tab::T + coeffs::NTuple{4, Float64} +end + +function BrineCO2MixingDensities(tab::T; coefficients = (37.51, −9.585*10e−2, 8.74*10e−4, −5.044*10e−7)) where T + return BrineCO2MixingDensities{T}(tab, coefficients) +end + +@jutul_secondary function update_density!(rho, rho_def::BrineCO2MixingDensities, model::SimulationModel{D, S}, Pressure, Temperature, LiquidMassFractions, ix) where {D, S<:CompositionalSystem} + # V = 10e−6*(37.51 − 9.585*10e−2*T + 8.74*10e−4*T^2 − 5.044*10e−7*T^3) + # M_co2 = 44.01*10^-3 + # ρ_co2 = M_co2/V + + c1, c2, c3, c4 = rho_def.coeffs + sys = model.system + eos = sys.equation_of_state + cnames = eos.mixture.component_names + # TODO: This is hard coded. + @assert cnames[1] == raw"CarbonDioxide" + @assert length(cnames) == 2 + l, v = phase_indices(sys) + @inbounds for i in ix + p = Pressure[i] + T = Temperature[i] + X_co2 = LiquidMassFractions[1, i] + X_h2o = LiquidMassFractions[2, i] + + rho_co2, rho_h2o_pure = rho_def.tab(p, T) + + vol_co2 = 10e−6*(c1 + c2*T + c3*T^2 + c4*T^3) + rho_liquid_co2_pure = 44.01*10^-3/vol_co2 + rho_brine_mix = rho_liquid_co2_pure*X_co2 + rho_h2o_pure*X_h2o + + rho[v, i] = rho_co2 + rho[l, i] = rho_brine_mix + end +end diff --git a/src/multicomponent/variables/saturations.jl b/src/multicomponent/variables/saturations.jl index d84d35eb..4d40a980 100644 --- a/src/multicomponent/variables/saturations.jl +++ b/src/multicomponent/variables/saturations.jl @@ -32,3 +32,41 @@ end end end +struct SaturationsFromDensities <: PhaseVariables + +end + + +@jutul_secondary function update_saturations!(Sat, s::SaturationsFromDensities, model::SimulationModel{D, S}, PhaseMassDensities, FlashResults, ix) where {D, S<:MultiPhaseCompositionalSystemLV{E, T, O} where {E, T, O<:Nothing}} + l, v = phase_indices(model.system) + @assert number_of_phases(model.system) == 2 + eos = model.system.equation_of_state + props = map(x -> x.mw, eos.mixture.properties) + @inbounds for i in ix + flash = FlashResults[i] + x = flash.liquid.mole_fractions + y = flash.vapor.mole_fractions + if flash.state == MultiComponentFlash.two_phase_lv + # Calculate values that are proportional to volume and get + # saturation from that. + mass_liquid = 0.0 + mass_vapor = 0.0 + for c in eachindex(props) + mw = props[i].mw + mass_liquid += mw*x[i] + mass_vapor += mw*y[i] + end + rho_l = PhaseMassDensities[l, i] + rho_l = PhaseMassDensities[v, i] + vol_liquid = mass_liquid/rho_l + vol_vapor = mass_vapor/rho_v + S_v = vol_vapor/(vol_liquid + vol_vapor) + elseif flash.state == MultiComponentFlash.single_phase_v + S_v = 1.0 + else + S_v = 0.0 + end + Sat[v, i] = S_v + Sat[l, i] = 1.0 - S_v + end +end From 56aa0e769777cbeba9d13c2ccec6df470e054026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 10:24:22 +0100 Subject: [PATCH 09/92] Fixes to CO2-brine PVT --- src/multicomponent/variables/density.jl | 23 +++++++++++++-------- src/multicomponent/variables/saturations.jl | 13 ++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/multicomponent/variables/density.jl b/src/multicomponent/variables/density.jl index c7402362..a1b36b09 100644 --- a/src/multicomponent/variables/density.jl +++ b/src/multicomponent/variables/density.jl @@ -55,19 +55,24 @@ end @assert cnames[1] == raw"CarbonDioxide" @assert length(cnames) == 2 l, v = phase_indices(sys) - @inbounds for i in ix + for i in ix p = Pressure[i] T = Temperature[i] X_co2 = LiquidMassFractions[1, i] - X_h2o = LiquidMassFractions[2, i] - rho_co2, rho_h2o_pure = rho_def.tab(p, T) - - vol_co2 = 10e−6*(c1 + c2*T + c3*T^2 + c4*T^3) - rho_liquid_co2_pure = 44.01*10^-3/vol_co2 - rho_brine_mix = rho_liquid_co2_pure*X_co2 + rho_h2o_pure*X_h2o - + rho_brine = co2_brine_mixture_density(T, c1, c2, c3, c4, rho_h2o_pure, X_co2) rho[v, i] = rho_co2 - rho[l, i] = rho_brine_mix + rho[l, i] = rho_brine end end + +function co2_brine_mixture_density(T, c1, c2, c3, c4, rho_h2o_pure, X_co2) + T -= 273.15 # Relation is in C, input is in Kelvin + vol_co2 = 10e−6*(c1 + c2*T + c3*T^2 + c4*T^3) + rho_liquid_co2_pure = 44.01e-3/vol_co2 + X_h2o = 1.0 - X_co2 + # Linear volume mixing rule + vol = X_co2/rho_liquid_co2_pure + X_h2o/rho_h2o_pure + # Return as density + return 1.0/vol +end diff --git a/src/multicomponent/variables/saturations.jl b/src/multicomponent/variables/saturations.jl index 4d40a980..c2b7d64b 100644 --- a/src/multicomponent/variables/saturations.jl +++ b/src/multicomponent/variables/saturations.jl @@ -41,7 +41,7 @@ end l, v = phase_indices(model.system) @assert number_of_phases(model.system) == 2 eos = model.system.equation_of_state - props = map(x -> x.mw, eos.mixture.properties) + molar_masses = map(x -> x.mw, eos.mixture.properties) @inbounds for i in ix flash = FlashResults[i] x = flash.liquid.mole_fractions @@ -51,15 +51,16 @@ end # saturation from that. mass_liquid = 0.0 mass_vapor = 0.0 - for c in eachindex(props) - mw = props[i].mw + for c in eachindex(molar_masses) + mw = molar_masses[i] mass_liquid += mw*x[i] mass_vapor += mw*y[i] end rho_l = PhaseMassDensities[l, i] - rho_l = PhaseMassDensities[v, i] - vol_liquid = mass_liquid/rho_l - vol_vapor = mass_vapor/rho_v + rho_v = PhaseMassDensities[v, i] + V = flash.V + vol_liquid = (1.0-V)*mass_liquid/rho_l + vol_vapor = V*mass_vapor/rho_v S_v = vol_vapor/(vol_liquid + vol_vapor) elseif flash.state == MultiComponentFlash.single_phase_v S_v = 1.0 From 4e0e62f6fadb92992b00b66660bef0f3c8387ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 10:32:33 +0100 Subject: [PATCH 10/92] Add P-T tabulated viscosities --- src/multicomponent/variables/viscosity.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/multicomponent/variables/viscosity.jl b/src/multicomponent/variables/viscosity.jl index f81d3841..dd5cf687 100644 --- a/src/multicomponent/variables/viscosity.jl +++ b/src/multicomponent/variables/viscosity.jl @@ -30,3 +30,17 @@ end mu[a, i] = viscosity(pvt, p) end end + +struct PTViscosities{T} <: AbstractCompositionalViscosities + tab::T +end + +@jutul_secondary function update_viscosity!(mu, mu_def::PTViscosities, model, Pressure, Temperature, ix) + tab = mu_def.tab + @inbounds for i in ix + p = Pressure[i] + T = Temperature[i] + μ = tab(p, T) + @. mu[:, i] = μ + end +end From a04b33746285c8ab1ac4173a53083ec183b906d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 11:21:19 +0100 Subject: [PATCH 11/92] Update flash.jl --- src/multicomponent/variables/flash.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/multicomponent/variables/flash.jl b/src/multicomponent/variables/flash.jl index dd2f8f0a..73c8ce20 100644 --- a/src/multicomponent/variables/flash.jl +++ b/src/multicomponent/variables/flash.jl @@ -296,19 +296,21 @@ function add_derivatives_to_vapor_fraction_rachford_rice(V::Float64, K, z, K_val N = length(z) V0 = V V = convert(T, V) - ∂V = V.partials + ∂R_chain = V.partials if Kt == T for i in 1:N dK_i = MultiComponentFlash.objectiveRR_dK(V0, K_val, z_val, i) - ∂V += K[i].partials*dK_i + ∂R_chain += K[i].partials*dK_i end end if Zt == T for i in 1:N dz_i = MultiComponentFlash.objectiveRR_dz(V0, K_val, z_val, i) - ∂V += z[i].partials*dz_i + ∂R_chain += z[i].partials*dz_i end end + ∂R_dV = MultiComponentFlash.objectiveRR_dV(V0, K_val, z_val) + ∂V = -∂R_chain/∂R_dV V = T(V0, ∂V) end return V From 232a6d250558a5157602408d4f468cc52bfacfb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 13:16:44 +0100 Subject: [PATCH 12/92] Fix typo in saturations computed from density --- src/multicomponent/variables/saturations.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/multicomponent/variables/saturations.jl b/src/multicomponent/variables/saturations.jl index c2b7d64b..40d2edcf 100644 --- a/src/multicomponent/variables/saturations.jl +++ b/src/multicomponent/variables/saturations.jl @@ -52,9 +52,9 @@ end mass_liquid = 0.0 mass_vapor = 0.0 for c in eachindex(molar_masses) - mw = molar_masses[i] - mass_liquid += mw*x[i] - mass_vapor += mw*y[i] + mw = molar_masses[c] + mass_liquid += mw*x[c] + mass_vapor += mw*y[c] end rho_l = PhaseMassDensities[l, i] rho_v = PhaseMassDensities[v, i] From 77fb9c80e8af0af65178d25e77244c259e16dfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 19:37:49 +0100 Subject: [PATCH 13/92] Add KValueWrapper type --- src/multicomponent/utils.jl | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/multicomponent/utils.jl b/src/multicomponent/utils.jl index c37e1083..8ed61a33 100644 --- a/src/multicomponent/utils.jl +++ b/src/multicomponent/utils.jl @@ -16,3 +16,42 @@ has_other_phase(sys::MultiPhaseCompositionalSystemLV{E, T, O}) where {E, T, O<:N phase_indices(sys::MultiPhaseCompositionalSystemLV{E, T, O}) where {E, T, O<:Nothing} = (liquid_phase_index(sys), vapor_phase_index(sys)) phase_indices(sys::MultiComponentSystem) = (other_phase_index(sys), liquid_phase_index(sys), vapor_phase_index(sys)) + +export KValueWrapper +struct KValueWrapper{T, D} + K::T +end + +""" + KValueWrapper(K; dependence::Symbol = :pT) + +Create a wrapper for a K-value interpolator to be used with K-value flash. + +The main purpose of this wrapper is to transform the general flash cond +NamedTuple into the right arguments for multi-linear interpolation. +""" +function KValueWrapper(K::T; dependence::Symbol = :pT) where T + choices = (:p, :T, :pT, :pTz) + dependence in choices || throw(ArgumentError("Bad input: dependence = $dependence was not in $choices")) + return KValueWrapper{T, dependence}(K) +end + +function (k::KValueWrapper{<:Any, :p})(cond::NamedTuple) + val = k.K(cond.p) + return val +end + +function (k::KValueWrapper{<:Any, :T})(cond::NamedTuple) + val = k.K(cond.T) + return val +end + +function (k::KValueWrapper{<:Any, :pT})(cond::NamedTuple) + val = k.K(cond.p, cond.T) + return val +end + +function (k::KValueWrapper{<:Any, :T})(cond::NamedTuple) + val = k.K(cond.p, cond.T, cond.z) + return val +end From 5d8a8bc4de5efb8b1e4c113397d0bfda3f7c4434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 20:36:04 +0100 Subject: [PATCH 14/92] Swap default component order for brine mixture --- src/multicomponent/variables/density.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/multicomponent/variables/density.jl b/src/multicomponent/variables/density.jl index a1b36b09..7d66f65c 100644 --- a/src/multicomponent/variables/density.jl +++ b/src/multicomponent/variables/density.jl @@ -52,14 +52,15 @@ end eos = sys.equation_of_state cnames = eos.mixture.component_names # TODO: This is hard coded. - @assert cnames[1] == raw"CarbonDioxide" + @assert cnames[1] == raw"H₂O" "First component was $(cnames[1]), expected H₂O" + @assert cnames[2] == raw"CO₂" "Second component was $(cnames[1]), expected CO₂" @assert length(cnames) == 2 l, v = phase_indices(sys) for i in ix p = Pressure[i] T = Temperature[i] - X_co2 = LiquidMassFractions[1, i] - rho_co2, rho_h2o_pure = rho_def.tab(p, T) + X_co2 = LiquidMassFractions[2, i] + rho_h2o_pure, rho_co2 = rho_def.tab(p, T) rho_brine = co2_brine_mixture_density(T, c1, c2, c3, c4, rho_h2o_pure, X_co2) rho[v, i] = rho_co2 rho[l, i] = rho_brine From bce63217f39e698f9449e1504dcd34153b01cea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 20:49:42 +0100 Subject: [PATCH 15/92] Remove runtime dispatch from loop --- src/multicomponent/variables/flash.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/multicomponent/variables/flash.jl b/src/multicomponent/variables/flash.jl index 73c8ce20..140de7a7 100644 --- a/src/multicomponent/variables/flash.jl +++ b/src/multicomponent/variables/flash.jl @@ -242,7 +242,11 @@ function flash_entity_loop!(flash_results, fr, model, eos::KValuesEOS, Pressure, storage, m, buffers = fr.storage, fr.method, fr.update_buffer S, buf = thread_buffers(storage, buffers) z_buf = buf.z - for i in ix + kvalue_loop!(flash_results, ix, Pressure, Temperature, OverallMoleFractions, eos, z_buf) +end + +function kvalue_loop!(flash_results, ix, Pressure, Temperature, OverallMoleFractions, eos, z_buf) + @inbounds for i in ix P = Pressure[i] T = Temperature[i] Z = @view OverallMoleFractions[:, i] From daf54089b3d54dd8e0b82e3389fcd5fa5c1be449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 21:17:42 +0100 Subject: [PATCH 16/92] Clean up brine density --- src/multicomponent/variables/density.jl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/multicomponent/variables/density.jl b/src/multicomponent/variables/density.jl index 7d66f65c..c1c8ed95 100644 --- a/src/multicomponent/variables/density.jl +++ b/src/multicomponent/variables/density.jl @@ -38,15 +38,11 @@ struct BrineCO2MixingDensities{T} <: AbstractCompositionalDensities coeffs::NTuple{4, Float64} end -function BrineCO2MixingDensities(tab::T; coefficients = (37.51, −9.585*10e−2, 8.74*10e−4, −5.044*10e−7)) where T +function BrineCO2MixingDensities(tab::T; coefficients = (37.51, −9.585e−2, 8.74e−4, −5.044e−7)) where T return BrineCO2MixingDensities{T}(tab, coefficients) end @jutul_secondary function update_density!(rho, rho_def::BrineCO2MixingDensities, model::SimulationModel{D, S}, Pressure, Temperature, LiquidMassFractions, ix) where {D, S<:CompositionalSystem} - # V = 10e−6*(37.51 − 9.585*10e−2*T + 8.74*10e−4*T^2 − 5.044*10e−7*T^3) - # M_co2 = 44.01*10^-3 - # ρ_co2 = M_co2/V - c1, c2, c3, c4 = rho_def.coeffs sys = model.system eos = sys.equation_of_state @@ -69,7 +65,7 @@ end function co2_brine_mixture_density(T, c1, c2, c3, c4, rho_h2o_pure, X_co2) T -= 273.15 # Relation is in C, input is in Kelvin - vol_co2 = 10e−6*(c1 + c2*T + c3*T^2 + c4*T^3) + vol_co2 = 1e−6*(c1 + c2*T + c3*T^2 + c4*T^3) rho_liquid_co2_pure = 44.01e-3/vol_co2 X_h2o = 1.0 - X_co2 # Linear volume mixing rule From c3acd436175d3388787bffaa1c225c9c1ae2d98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 21:32:30 +0100 Subject: [PATCH 17/92] Improve RESV weight calculation --- src/facility/types.jl | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/facility/types.jl b/src/facility/types.jl index 6b9e7bd9..0dd778bc 100644 --- a/src/facility/types.jl +++ b/src/facility/types.jl @@ -504,8 +504,16 @@ function realize_control_for_reservoir(rstate, ctrl::ProducerControl{<:Historica ww = w[a] wo = w[l] wg = w[v] - rs = min(wg/wo, rs_avg) - rv = min(wo/wg, rv_avg) + if wo <= 1e-20 + rs = 0.0 + else + rs = min(wg/wo, rs_avg) + end + if wg <= 1e-20 + rv = 0.0 + else + rv = min(wo/wg, rv_avg) + end svar = Jutul.get_secondary_variables(model) b_var = svar[:ShrinkageFactors] @@ -522,8 +530,8 @@ function realize_control_for_reservoir(rstate, ctrl::ProducerControl{<:Historica bG = shrinkage(b_var.pvt[v], reg, p_avg, 1) end - shrink = 1.0 - rs*rv - shrink_avg = 1.0 - rs_avg*rv_avg + shrink = max(1.0 - rs*rv, 1e-20) + shrink_avg = max(1.0 - rs_avg*rv_avg, 1e-20) old_rate = ctrl.target.value # Water new_water_rate = old_rate*ww/bW @@ -531,11 +539,11 @@ function realize_control_for_reservoir(rstate, ctrl::ProducerControl{<:Historica # Oil qo = old_rate*wo new_oil_rate = qo - new_oil_weight = 1/(bO*shrink_avg) + new_oil_weight = 1.0/(bO*shrink_avg) # Gas qg = old_rate*wg new_gas_rate = qg - new_gas_weight = 1/(bG*shrink_avg) + new_gas_weight = 1.0/(bG*shrink_avg) # Miscibility adjustments if vapoil new_oil_rate -= rv*qg @@ -550,6 +558,8 @@ function realize_control_for_reservoir(rstate, ctrl::ProducerControl{<:Historica new_weights = (new_water_weight, new_oil_weight, new_gas_weight) + @assert all(isfinite, new_weights) "Computed RESV weights were non-finite: $new_weights" + new_rate = new_water_rate + new_oil_rate + new_gas_rate new_control = replace_target(ctrl, ReservoirVoidageTarget(new_rate, new_weights)) return (new_control, true) From 2c7972d0b366449a6824b38d470d1e0777fe7cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 13 Feb 2024 21:32:36 +0100 Subject: [PATCH 18/92] Fix typo in assert --- src/multicomponent/variables/density.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multicomponent/variables/density.jl b/src/multicomponent/variables/density.jl index c1c8ed95..64a960e5 100644 --- a/src/multicomponent/variables/density.jl +++ b/src/multicomponent/variables/density.jl @@ -49,7 +49,7 @@ end cnames = eos.mixture.component_names # TODO: This is hard coded. @assert cnames[1] == raw"H₂O" "First component was $(cnames[1]), expected H₂O" - @assert cnames[2] == raw"CO₂" "Second component was $(cnames[1]), expected CO₂" + @assert cnames[2] == raw"CO₂" "Second component was $(cnames[2]), expected CO₂" @assert length(cnames) == 2 l, v = phase_indices(sys) for i in ix From 27505bdb1f062e8232cda813f487784144ca62bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 14 Feb 2024 10:56:03 +0100 Subject: [PATCH 19/92] Cleanup in compositional --- src/multicomponent/variables/others.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/multicomponent/variables/others.jl b/src/multicomponent/variables/others.jl index 5d026d1d..1cdb2f43 100644 --- a/src/multicomponent/variables/others.jl +++ b/src/multicomponent/variables/others.jl @@ -23,10 +23,10 @@ end @inline function update_mass_fractions!(X, x, cell, molar_masses) t = zero(eltype(X)) - @inbounds for i in eachindex(x) + @inbounds for i in 1:length(x) t += molar_masses[i] * x[i] end - @inbounds for i in eachindex(x) + @inbounds for i in 1:length(x) X[i, cell] = molar_masses[i] * x[i] / t end end @@ -134,9 +134,6 @@ function single_phase_mass!(M, ρ, S, mass_fractions, Φ, cell, N, phase) if S_eos < MINIMUM_COMPOSITIONAL_SATURATION S_eos = replace_value(S_eos, MINIMUM_COMPOSITIONAL_SATURATION) end - # S_eos = max(S[phase, cell], MINIMUM_COMPOSITIONAL_SATURATION) - # @info "?? $phase" S[phase, cell] MINIMUM_COMPOSITIONAL_SATURATION S_eos - @inbounds M_l = ρ[phase, cell] * S_eos for c in 1:N @inbounds M[c, cell] = M_l*mass_fractions[c, cell]*Φ[cell] From 6461f5e51fda1200c9817e48183c91d801620b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 14 Feb 2024 12:31:57 +0100 Subject: [PATCH 20/92] Some small performance increases for k values --- src/multicomponent/variables/flash.jl | 20 +++++++++++++++----- src/regions/regions.jl | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/multicomponent/variables/flash.jl b/src/multicomponent/variables/flash.jl index 140de7a7..67cd971f 100644 --- a/src/multicomponent/variables/flash.jl +++ b/src/multicomponent/variables/flash.jl @@ -266,8 +266,11 @@ function k_value_flash!(result::FR, eos, P, T, Z, z) where FR K = result.K x = result.liquid.mole_fractions y = result.vapor.mole_fractions + ncomp = length(z) - @. K = value(K_ad) + @inbounds for i in 1:ncomp + K[i] = value(K_ad[i]) + end V = flash_2ph!(nothing, K, eos, c) pure_liquid = V <= 0.0 @@ -278,14 +281,21 @@ function k_value_flash!(result::FR, eos, P, T, Z, z) where FR else phase_state = MultiComponentFlash.single_phase_l end - @. x = Z - @. y = Z + @inbounds for i in 1:ncomp + Z_i = Z[i] + x[i] = Z_i + y[i] = Z_i + end V = convert(Num_t, clamp(V, 0.0, 1.0)) else phase_state = MultiComponentFlash.two_phase_lv V = add_derivatives_to_vapor_fraction_rachford_rice(V, K_ad, Z, K, z) - @. x = liquid_mole_fraction(Z, K, V) - @. y = vapor_mole_fraction(x, K) + @inbounds for i in 1:ncomp + K_i = K[i] + x_i = liquid_mole_fraction(Z[i], K_i, V) + x[i] = x_i + y[i] = vapor_mole_fraction(x_i, K_i) + end end Z_L = Z_V = convert(Num_t, 1.0) new_result = FlashedMixture2Phase(phase_state, K, V, x, y, Z_L, Z_V) diff --git a/src/regions/regions.jl b/src/regions/regions.jl index 75120a07..fcdeb5e5 100644 --- a/src/regions/regions.jl +++ b/src/regions/regions.jl @@ -21,7 +21,7 @@ end @inline number_of_regions(regions::Nothing) = 1 @inline number_of_regions(regions::AbstractVector) = 1 -@inline function table_by_region(tab, reg) +Base.@propagate_inbounds @inline function table_by_region(tab, reg) return tab[reg] end From 6d3c59bede0cf1254802121eff490fb3e56015e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 14 Feb 2024 12:56:57 +0100 Subject: [PATCH 21/92] Add a utility, clean up to kr --- src/regions/regions.jl | 8 ++++++++ src/types.jl | 7 +++---- src/variables/relperm.jl | 7 +++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/regions/regions.jl b/src/regions/regions.jl index fcdeb5e5..4a834955 100644 --- a/src/regions/regions.jl +++ b/src/regions/regions.jl @@ -21,6 +21,14 @@ end @inline number_of_regions(regions::Nothing) = 1 @inline number_of_regions(regions::AbstractVector) = 1 +Base.@propagate_inbounds @inline function evaluate_table_by_region(tab, reg, arg...) + return tab[reg](arg...) +end + +Base.@propagate_inbounds @inline function evaluate_table_by_region(tab, ::Nothing, arg...) + return tab(arg...) +end + Base.@propagate_inbounds @inline function table_by_region(tab, reg) return tab[reg] end diff --git a/src/types.jl b/src/types.jl index cda2ac6d..7d4f6c43 100644 --- a/src/types.jl +++ b/src/types.jl @@ -244,9 +244,8 @@ equal length `s` and `k`): ``K_r = K(S)`` -Optionally, a label for the phase, the connate -saturation and a small epsilon value used to avoid extrapolation can be -specified. +Optionally, a label for the phase, the connate saturation and a small epsilon +value used to avoid extrapolation can be specified. """ function PhaseRelativePermeability(s, k; label = :w, connate = s[1], epsilon = 1e-16) msg(i) = "k = $(k[i]) at entry $i corresponding to saturation $(s[i])" @@ -271,7 +270,7 @@ function PhaseRelativePermeability(s, k; label = :w, connate = s[1], epsilon = 1 crit = s[crit_ix] s, k = JutulDarcy.add_missing_endpoints(s, k) JutulDarcy.ensure_endpoints!(s, k, epsilon) - kr = get_1d_interpolator(s, k, cap_endpoints = false) + kr = get_1d_interpolator(s, k, cap_endpoints = false, constant_dx = false) return PhaseRelativePermeability(kr, label, connate, crit, s_max, k_max) end diff --git a/src/variables/relperm.jl b/src/variables/relperm.jl index 0d2478c8..8b02e9d8 100644 --- a/src/variables/relperm.jl +++ b/src/variables/relperm.jl @@ -430,12 +430,11 @@ end Base.@propagate_inbounds function two_phase_relperm!(kr, s, regions, Kr_1, Kr_2, phases, c) i1, i2 = phases reg = region(regions, c) - kr1 = table_by_region(Kr_1, reg) sw = s[i1, c] - kr[i1, c] = kr1(sw) - kr2 = table_by_region(Kr_2, reg) sg = s[i2, c] - kr[i2, c] = kr2(sg) + + kr[i1, c] = evaluate_table_by_region(Kr_1, reg, sw) + kr[i2, c] = evaluate_table_by_region(Kr_2, reg, sg) end @jutul_secondary function update_kr_with_scaling!(kr, relperm::ReservoirRelativePermeabilities{<:Any, :wog}, model, Saturations, RelPermScalingW, RelPermScalingOW, RelPermScalingOG, RelPermScalingG, ConnateWater, ix) From 3a98b849778c6b8874e2b9d6446afc8fa92f2d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 14 Feb 2024 13:03:48 +0100 Subject: [PATCH 22/92] Update mrst_input.jl --- src/input_simulation/mrst_input.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index 339f7f23..c00da6ae 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -485,7 +485,12 @@ function deck_pc(props; oil, water, gas, satnum = nothing) s = s[ix] end s, pc = saturation_table_handle_defaults(s, pc) - interp_ow = get_1d_interpolator(s, pc) + if length(T) == 1 + constant_dx = missing + else + constant_dx = false + end + interp_ow = get_1d_interpolator(s, pc, constant_dx = constant_dx) push!(PC, interp_ow) end out = Tuple(PC) From 951c8b071651a4e65f7e478265f4554765a64b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 15 Feb 2024 14:13:31 +0100 Subject: [PATCH 23/92] Add missing function for composite system --- src/multiphase.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/multiphase.jl b/src/multiphase.jl index effd337f..364733f6 100644 --- a/src/multiphase.jl +++ b/src/multiphase.jl @@ -10,6 +10,7 @@ flow_system(sys::MultiPhaseSystem) = sys flow_system(sys::CompositeSystem) = sys.systems.flow number_of_components(sys::ImmiscibleSystem) = number_of_phases(sys) +number_of_components(sys::CompositeSystem) = number_of_components(flow_system(sys)) # Single-phase From 01f4b2a2194d1cb9c04f528951e23eca391a0bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 15 Feb 2024 14:52:03 +0100 Subject: [PATCH 24/92] A bunch of smaller thermal fixes --- src/multicomponent/utils.jl | 3 ++- src/multicomponent/variables/flash.jl | 2 +- src/thermal/thermal.jl | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/multicomponent/utils.jl b/src/multicomponent/utils.jl index 8ed61a33..e2f27064 100644 --- a/src/multicomponent/utils.jl +++ b/src/multicomponent/utils.jl @@ -11,7 +11,8 @@ function number_of_components(sys::MultiPhaseCompositionalSystemLV{E, T, O, G, N end phase_index(sys, phase) = only(findfirst(isequal(phase), sys.phases)) -has_other_phase(sys) = true +has_other_phase(sys) = number_of_phases(sys) > 2 +has_other_phase(sys::CompositeSystem) = has_other_phase(flow_system(sys)) has_other_phase(sys::MultiPhaseCompositionalSystemLV{E, T, O}) where {E, T, O<:Nothing} = false phase_indices(sys::MultiPhaseCompositionalSystemLV{E, T, O}) where {E, T, O<:Nothing} = (liquid_phase_index(sys), vapor_phase_index(sys)) diff --git a/src/multicomponent/variables/flash.jl b/src/multicomponent/variables/flash.jl index 67cd971f..7fc1ae7a 100644 --- a/src/multicomponent/variables/flash.jl +++ b/src/multicomponent/variables/flash.jl @@ -62,7 +62,7 @@ function initialize_variable_ad!(state, model, pvar::FlashResults, symb, npartia n = number_of_entities(model, pvar) v_ad = get_ad_entity_scalar(1.0, npartials, diag_pos; kwarg...) ∂T = typeof(v_ad) - eos = model.system.equation_of_state + eos = flow_system(model.system).equation_of_state r = FlashedMixture2Phase(eos, ∂T) T = typeof(r) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 64fbe44a..581d33de 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -1,19 +1,30 @@ """ - ThermalSystem(num_phases = 1, formulation = :Temperature) + ThermalSystem(number_of_phases = 1, number_of_components = number_of_phases, formulation = :Temperature) Geothermal system that defines heat transfer through fluid advection and through the rock itself. Can be combined with a multiphase system using [`Jutul.CompositeSystem`](@ref). """ struct ThermalSystem{T} <: JutulSystem nph::Int64 - function ThermalSystem(; nphases = 1, formulation = :Temperature) + ncomp::Int64 + function ThermalSystem(; + number_of_phases = 1, + number_of_components = number_of_phases, + formulation = :Temperature + ) @assert formulation == :Temperature - new{formulation}(nphases) + new{formulation}(number_of_phases, number_of_components) end end +function ThermalSystem(sys::MultiPhaseSystem; kwarg...) + nph = number_of_phases(sys) + nc = number_of_components(sys) + return ThermalSystem(number_of_phases = nph, number_of_components = nc; kwarg...) +end + thermal_system(sys::ThermalSystem) = sys thermal_system(sys::CompositeSystem) = sys.systems.thermal @@ -113,6 +124,7 @@ function Jutul.default_parameter_values(data_domain, model, param::RockThermalCo end number_of_phases(t::ThermalSystem) = t.nph +number_of_components(t::ThermalSystem) = t.ncomp function select_primary_variables!(S, system::ThermalSystem, model) S[:Temperature] = Temperature() From b4e9f175159a663aed33060d46dfa577f9c5efc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 15 Feb 2024 16:37:17 +0100 Subject: [PATCH 25/92] Fix error in mole fraction increment norm --- src/multicomponent/variables/primary.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multicomponent/variables/primary.jl b/src/multicomponent/variables/primary.jl index a99b5863..bb8cca13 100644 --- a/src/multicomponent/variables/primary.jl +++ b/src/multicomponent/variables/primary.jl @@ -49,7 +49,7 @@ function Jutul.increment_norm(dX, state, model, X, pvar::OverallMoleFractions) N = degrees_of_freedom_per_entity(model, pvar) for i in axes(dX, 1) for j in axes(dX, 2) - cell = active[i] + cell = active[j] dx = dX[i, j] s = get_scaling(sw, cell) From 8217cea93e68e0967addd80291ae524336f00dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 16 Feb 2024 13:22:06 +0100 Subject: [PATCH 26/92] Improvement to endscale setup --- src/input_simulation/mrst_input.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index c00da6ae..a9b173c5 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -366,7 +366,11 @@ end function deck_relperm(props; oil, water, gas, satnum = nothing) if haskey(props, "SCALECRS") - if length(props["SCALECRS"]) == 0 || lowercase(first(props["SCALECRS"])) == "no" + scalecrs = props["SCALECRS"] + if scalecrs isa String + scalecrs = [scalecrs] + end + if length(scalecrs) == 0 || lowercase(first(scalecrs)) == "no" @info "Found two-point rel. perm. scaling" scaling = TwoPointKrScale else From e78e653cf22d5997a0d41c2c54d017e318dddef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 16 Feb 2024 13:22:28 +0100 Subject: [PATCH 27/92] Avoid crash for "nothing" control when split_wells = true --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 583e6220..15a30b4f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -741,7 +741,7 @@ function setup_reservoir_forces(model::MultiModel; control = nothing, limits = n else new_forces = Dict{Symbol, Any}() for (k, m) in pairs(submodels) - if m isa SimpleWellFlowModel || m isa MSWellFlowModel + if model_or_domain_is_well(m) && !isnothing(control) ctrl_symbol = Symbol("$(k)_ctrl") @assert haskey(submodels, ctrl_symbol) "Controller for well $k must be present with the name $ctrl_symbol" subctrl = Dict{Symbol, Any}() From 081253cdc3e240199134a3ddde856751253d7f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 16 Feb 2024 13:31:19 +0100 Subject: [PATCH 28/92] Graceful handling of missing increments This will only happen when a solver is really broken but still worth fixing --- src/multicomponent/multicomponent.jl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/multicomponent/multicomponent.jl b/src/multicomponent/multicomponent.jl index a69c0cc1..f3696fb7 100644 --- a/src/multicomponent/multicomponent.jl +++ b/src/multicomponent/multicomponent.jl @@ -112,13 +112,23 @@ end function pressure_increments(model, state, update_report) max_p = maximum(value, state.Pressure) dp = update_report[:Pressure] - dp_abs = dp.max + if haskey(dp, :max) + dp_abs = dp.max + else + dp_abs = Inf + end dp_rel = dp_abs/max_p return (dp_abs, dp_rel) end function compositional_increment(model, state, update_report) - return update_report[:OverallMoleFractions].max_scaled + mf_report = update_report[:OverallMoleFractions] + if haskey(mf_report, :max_scaled) + v = mf_report.max_scaled + else + v = Inf + end + return v end function immiscible_increment(model, state, ::Missing) From 5359ee55408134d47957c6dd26c3228bbfe7faa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 16 Feb 2024 14:23:13 +0100 Subject: [PATCH 29/92] Avoid extra parameters for wells in thermal --- src/thermal/thermal.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 581d33de..59665073 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -143,9 +143,11 @@ function select_parameters!(S, system::ThermalSystem, model) S[:PhaseMassDensities] = ConstantCompressibilityDensities(nph) S[:Pressure] = Pressure() S[:Saturations] = Saturations() - S[:RelativePermeabilities] = RelativePermeabilitiesParameter() S[:PhaseViscosities] = PhaseViscosities() - S[:PhaseMassMobilities] = PhaseMassMobilities() + if !model_or_domain_is_well(model) + S[:PhaseMassMobilities] = PhaseMassMobilities() + S[:RelativePermeabilities] = RelativePermeabilitiesParameter() + end end function select_parameters!(prm, disc::D, model::ThermalModel) where D<:Union{TwoPointPotentialFlowHardCoded, Jutul.PotentialFlow} From ebeb923e819be4a7854b64c56004b1342ef38cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 16 Feb 2024 15:47:10 +0100 Subject: [PATCH 30/92] Update multicomponent.jl --- src/multicomponent/multicomponent.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/multicomponent/multicomponent.jl b/src/multicomponent/multicomponent.jl index f3696fb7..2f892bdf 100644 --- a/src/multicomponent/multicomponent.jl +++ b/src/multicomponent/multicomponent.jl @@ -112,11 +112,7 @@ end function pressure_increments(model, state, update_report) max_p = maximum(value, state.Pressure) dp = update_report[:Pressure] - if haskey(dp, :max) - dp_abs = dp.max - else - dp_abs = Inf - end + dp_abs = dp.max dp_rel = dp_abs/max_p return (dp_abs, dp_rel) end @@ -125,8 +121,10 @@ function compositional_increment(model, state, update_report) mf_report = update_report[:OverallMoleFractions] if haskey(mf_report, :max_scaled) v = mf_report.max_scaled + elseif haskey(mf_report, :max) + v = mf_report.max else - v = Inf + v = 1.0 end return v end From 48b5626b8e147ed780813d503ac7fc54ed3e5ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 19 Feb 2024 20:26:09 +0100 Subject: [PATCH 31/92] Fixes to dt/well mix parsing --- src/input_simulation/data_input.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 3c9d0f36..3d935192 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -734,7 +734,10 @@ function parse_control_steps(runspec, props, schedule, sys) end current_time = 0.0 function add_dt!(dt, ctrl_ix) - @assert dt > 0.0 + if dt ≈ 0 + return + end + @assert dt > 0.0 "dt must be positive, attempt to add dt number $(length(tstep)) was $dt at control $ctrl_ix" push!(tstep, dt) push!(cstep, ctrl_ix) end @@ -1048,7 +1051,7 @@ function select_injector_mixture_spec(sys::Union{ImmiscibleSystem, StandardBlack if phase == LiquidPhase() v = Float64(type == "OIL") elseif phase == AqueousPhase() - v = Float64(type == "WATER") + v = Float64(type == "WATER" || type == "WAT") else @assert phase isa VaporPhase v = Float64(type == "GAS") @@ -1056,7 +1059,7 @@ function select_injector_mixture_spec(sys::Union{ImmiscibleSystem, StandardBlack rho += rho_ph*v push!(mix, v) end - @assert sum(mix) ≈ 1.0 + @assert sum(mix) ≈ 1.0 "Expected mixture to sum to 1, was $mix for type $type (declared phases: $phases)" return (rho, mix) end From c9bfc94c8520e6eae7ab6a9d593d4688b23ef555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 19 Feb 2024 20:32:39 +0100 Subject: [PATCH 32/92] Turn parser error into warning --- src/input_simulation/data_input.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 3d935192..66d3d28e 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -818,11 +818,12 @@ function parse_control_steps(runspec, props, schedule, sys) bad_kw[key] = true end end - push!(all_compdat, deepcopy(compdat)) - push!(all_controls, deepcopy(controls)) - push!(all_limits, deepcopy(limits)) - if !found_time - error("Did not find supported time kw in step $ctrl_ix: Keys were $(keys(step))") + if found_time + push!(all_compdat, deepcopy(compdat)) + push!(all_controls, deepcopy(controls)) + push!(all_limits, deepcopy(limits)) + else + @warn "Did not find supported time kw in step $ctrl_ix: Keys were $(keys(step))." end end for k in keys(bad_kw) From a4f07d37fd3133509e657a88e6aad5377c492f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 19 Feb 2024 20:52:28 +0100 Subject: [PATCH 33/92] Handle welopen --- src/input_simulation/data_input.jl | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 66d3d28e..db05d563 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -711,6 +711,8 @@ function parse_control_steps(runspec, props, schedule, sys) cstep = Vector{Int}() compdat = Dict{String, OrderedDict}() controls = Dict{String, Any}() + # "Hidden" well control mirror used with WELOPEN logic + active_controls = Dict{String, Any}() limits = Dict{String, Any}() streams = Dict{String, Any}() well_injection = Dict{String, Any}() @@ -718,6 +720,7 @@ function parse_control_steps(runspec, props, schedule, sys) for k in keys(wells) compdat[k] = OrderedDict{NTuple{3, Int}, Any}() controls[k] = DisabledControl() + active_controls[k] = DisabledControl() limits[k] = nothing streams[k] = nothing well_injection[k] = nothing @@ -811,6 +814,13 @@ function parse_control_steps(runspec, props, schedule, sys) for wk in kword name = wk[1] controls[name], limits[name] = keyword_to_control(sys, streams, wk, key, factor = well_factor[name]) + if !(controls[name] isa DisabledControl) + active_controls[name] = controls[name] + end + end + elseif key == "WELOPEN" + for wk in kword + apply_welopen!(controls, compdat, wk, active_controls) end elseif key in skip # Already handled @@ -1258,3 +1268,46 @@ function well_completion_sortperm(domain, wspec, order_t0, wc, dir) @assert length(sorted) == n return sorted end + +function apply_welopen!(controls, compdat, wk, controls_if_active) + name, flag, I, J, K, first_num, last_num = wk + # TODO: Handle shut in a better way + flag = uppercase(flag) + @assert flag in ("OPEN", "SHUT", "STOP") + is_open = flag == "OPEN" + if I == J == K == first_num == last_num == -1 + # Applies to well + if is_open + controls[name] = controls_if_active[name] + else + controls[name] = DisabledControl() + end + else + cdat = compdat[name] + ijk = keys(cdat) + + first_num = max(first_num, 1) + if last_num < 1 + last_num = length(ijk) + end + for i in eachindex(ijk) + if i < first_num + continue + elseif i > last_num + continue + else + I_i, J_i, K_i = ijk[i] + current_cdat = cdat[ijk[i]] + is_match(ix, ix_i) = ix < 1 || ix_i == ix + if is_match(I, I_i) && is_match(J, J_i) && is_match(K, K_i) + c = OrderedDict{Symbol, Any}() + for (k, v) in current_cdat + c[k] = v + end + c[:open] = is_open + cdat[ijk[i]] = (; c...) + end + end + end + end +end From b071cb95b13ebf9cea15c846ce05b2e30486a616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 19 Feb 2024 20:58:49 +0100 Subject: [PATCH 34/92] Handle well temperature --- src/input_simulation/data_input.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index db05d563..fd744ba0 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -709,6 +709,7 @@ function parse_control_steps(runspec, props, schedule, sys) tstep = Vector{Float64}() cstep = Vector{Int}() + well_temp = Dict{String, Float64}() compdat = Dict{String, OrderedDict}() controls = Dict{String, Any}() # "Hidden" well control mirror used with WELOPEN logic @@ -725,6 +726,7 @@ function parse_control_steps(runspec, props, schedule, sys) streams[k] = nothing well_injection[k] = nothing well_factor[k] = 1.0 + well_temp[k] = 273.15 + 20.0 end all_compdat = [] all_controls = [] @@ -813,7 +815,7 @@ function parse_control_steps(runspec, props, schedule, sys) elseif key in ("WCONINJE", "WCONPROD", "WCONHIST", "WCONINJ", "WCONINJH") for wk in kword name = wk[1] - controls[name], limits[name] = keyword_to_control(sys, streams, wk, key, factor = well_factor[name]) + controls[name], limits[name] = keyword_to_control(sys, streams, wk, key, factor = well_factor[name], temperature = well_temp[name]) if !(controls[name] isa DisabledControl) active_controls[name] = controls[name] end @@ -822,6 +824,11 @@ function parse_control_steps(runspec, props, schedule, sys) for wk in kword apply_welopen!(controls, compdat, wk, active_controls) end + elseif key == "WTEMP" + for wk in kword + wnm, wt = wk + well_temp[wnm] = convert_to_si(wt, :Celsius) + end elseif key in skip # Already handled else @@ -932,7 +939,7 @@ function producer_limits(; bhp = Inf, lrat = Inf, orat = Inf, wrat = Inf, grat = return NamedTuple(pairs(lims)) end -function producer_control(sys, flag, ctrl, orat, wrat, grat, lrat, bhp; is_hist = false, kwarg...) +function producer_control(sys, flag, ctrl, orat, wrat, grat, lrat, bhp; is_hist = false, temperature = NaN, kwarg...) rho_s = reference_densities(sys) phases = get_phases(sys) From bc42901fdc6e964a1ad66cd583d89acbbd6b53d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 19 Feb 2024 21:01:08 +0100 Subject: [PATCH 35/92] Handle single phase "rel perm" --- src/input_simulation/mrst_input.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index a9b173c5..6143d4f0 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -365,6 +365,10 @@ function deck_pvt_gas(props; scaling = missing) end function deck_relperm(props; oil, water, gas, satnum = nothing) + if (water + oil + gas) == 1 + # Early return for single-phase. + return BrooksCoreyRelativePermeabilities(1) + end if haskey(props, "SCALECRS") scalecrs = props["SCALECRS"] if scalecrs isa String From 557b04c7b4a65e74056eb943faf1ace3ddd4f55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 19 Feb 2024 21:15:58 +0100 Subject: [PATCH 36/92] Handle single phase in equilibriation --- src/init/init.jl | 128 ++++++++++++++++++++++----------------- src/variables/relperm.jl | 1 + 2 files changed, 75 insertions(+), 54 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 7f7521ae..ad52e858 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -106,26 +106,32 @@ function equilibriate_state!(init, depths, model, sys, contacts, depth, datum_pr return rho end pressures = determine_hydrostatic_pressures(depths, depth, zmin, zmax, contacts, datum_pressure, density_f, contacts_pc) - s, pc = determine_saturations(depths, contacts, pressures; s_min = s_min, kwarg...) - - nc_total = number_of_cells(model.domain) - kr = zeros(nph, nc_total) - s_eval = zeros(nph, nc_total) - s_eval[:, cells] .= s - phases = get_phases(sys) - if length(phases) == 3 && AqueousPhase() in phases - swcon = zeros(nc_total) - if !ismissing(s_min) - swcon[cells] .= s_min[1] + if nph > 1 + s, pc = determine_saturations(depths, contacts, pressures; s_min = s_min, kwarg...) + + nc_total = number_of_cells(model.domain) + kr = zeros(nph, nc_total) + s_eval = zeros(nph, nc_total) + s_eval[:, cells] .= s + phases = get_phases(sys) + if length(phases) == 3 && AqueousPhase() in phases + swcon = zeros(nc_total) + if !ismissing(s_min) + swcon[cells] .= s_min[1] + end + JutulDarcy.update_kr!(kr, relperm, model, s_eval, swcon, cells) + else + JutulDarcy.update_kr!(kr, relperm, model, s_eval, cells) end - JutulDarcy.update_kr!(kr, relperm, model, s_eval, swcon, cells) + kr = kr[:, cells] + init[:Saturations] = s + init[:Pressure] = init_reference_pressure(pressures, contacts, kr, pc, 2) else - JutulDarcy.update_kr!(kr, relperm, model, s_eval, cells) + p = copy(vec(pressures)) + init[:Pressure] = p + init[:Saturations] = ones(1, length(p)) end - kr = kr[:, cells] - init[:Saturations] = s - init[:Pressure] = init_reference_pressure(pressures, contacts, kr, pc, 2) return init end @@ -135,6 +141,8 @@ function parse_state0_equil(model, datafile) has_oil = haskey(datafile["RUNSPEC"], "OIL") has_gas = haskey(datafile["RUNSPEC"], "GAS") + is_single_phase = (has_water + has_oil + has_gas) == 1 + sys = model.system d = model.data_domain has_sat_reg = haskey(d, :satnum) @@ -151,27 +159,33 @@ function parse_state0_equil(model, datafile) end eqlnum = model.data_domain[:eqlnum] - has_pc = haskey(model.secondary_variables, :CapillaryPressure) - if has_pc - pcvar = model.secondary_variables[:CapillaryPressure] - pc_functions = pcvar.pc - if has_sat_reg - @assert !isnothing(pcvar.regions) - @assert pcvar.regions == satnum - end - else + if is_single_phase + has_pc = false pc_functions = missing - end + krw_fn = missing + else + has_pc = haskey(model.secondary_variables, :CapillaryPressure) + if has_pc + pcvar = model.secondary_variables[:CapillaryPressure] + pc_functions = pcvar.pc + if has_sat_reg + @assert !isnothing(pcvar.regions) + @assert pcvar.regions == satnum + end + else + pc_functions = missing + end - if has_water - kr_var = model.secondary_variables[:RelativePermeabilities] - krw_fn = kr_var.krw - if has_sat_reg - @assert !isnothing(kr_var.regions) - @assert kr_var.regions == satnum + if has_water + kr_var = model.secondary_variables[:RelativePermeabilities] + krw_fn = kr_var.krw + if has_sat_reg + @assert !isnothing(kr_var.regions) + @assert kr_var.regions == satnum + end + else + krw_fn = missing end - else - krw_fn = missing end init = Dict{Symbol, Any}() @@ -240,29 +254,35 @@ function parse_state0_equil(model, datafile) else pc = nothing end - if has_water - krw = table_by_region(krw_fn, sreg) - if haskey(datafile, "PROPS") && haskey(datafile["PROPS"], "SWL") - swl = vec(datafile["PROPS"]["SWL"]) - swcon = swl[actnum_ix_for_reg] - else - swcon = fill(krw.connate, ncells_reg) - end - push!(s_min, swcon) - push!(s_max, ones(ncells_reg)) - @. non_connate -= swcon - end - if has_oil - push!(s_min, zeros(ncells_reg)) - push!(s_max, non_connate) - end - if has_gas + if is_single_phase push!(s_min, zeros(ncells_reg)) - push!(s_max, non_connate) + push!(s_max, ones(ncells_reg)) + else + if has_water + krw = table_by_region(krw_fn, sreg) + if haskey(datafile, "PROPS") && haskey(datafile["PROPS"], "SWL") + swl = vec(datafile["PROPS"]["SWL"]) + swcon = swl[actnum_ix_for_reg] + else + swcon = fill(krw.connate, ncells_reg) + end + push!(s_min, swcon) + push!(s_max, ones(ncells_reg)) + @. non_connate -= swcon + end + if has_oil + push!(s_min, zeros(ncells_reg)) + push!(s_max, non_connate) + end + if has_gas + push!(s_min, zeros(ncells_reg)) + push!(s_max, non_connate) + end end if nph == 1 - error("Not implemented.") + contacts = [] + contacts_pc = [] elseif nph == 2 if has_oil && has_gas contacts = (goc, ) @@ -415,7 +435,7 @@ end function determine_hydrostatic_pressures(depths, depth, zmin, zmax, contacts, datum_pressure, density_f, contacts_pc) nc = length(depths) nph = length(contacts) + 1 - ref_ix = 2 + ref_ix = min(2, nph) I_ref = phase_pressure_depth_table(depth, zmin, zmax, datum_pressure, density_f, ref_ix) pressures = zeros(nph, nc) pos = 1 diff --git a/src/variables/relperm.jl b/src/variables/relperm.jl index 8b02e9d8..2820b054 100644 --- a/src/variables/relperm.jl +++ b/src/variables/relperm.jl @@ -278,6 +278,7 @@ function Base.getindex(m::ReservoirRelativePermeabilities, s::Symbol) end scaling_type(::ReservoirRelativePermeabilities{T}) where T = T +scaling_type(::AbstractRelativePermeabilities) = NoKrScale function Jutul.line_plot_data(model::SimulationModel, k::ReservoirRelativePermeabilities) s = collect(0:0.01:1) From 1cc711076a81f88eb6015b484ba8f75ee8111f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 19 Feb 2024 21:27:50 +0100 Subject: [PATCH 37/92] Some thermal scaffolding --- src/init/init.jl | 12 ++++++++++-- src/input_simulation/data_input.jl | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index ad52e858..1ca60fcf 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -21,7 +21,7 @@ function equilibriate_state(model, contacts, if ismissing(datum_depth) datum_depth = pmin end - sys = model.system + sys = flow_system(model.system) init = Dict{Symbol, Any}() init = equilibriate_state!(init, pts, model, sys, contacts, datum_depth, datum_pressure; @@ -85,8 +85,16 @@ function equilibriate_state!(init, depths, model, sys, contacts, depth, datum_pr rhoOS, rhoGS = rho_s end end - pvt = model.secondary_variables[:PhaseMassDensities].pvt + rho = model.secondary_variables[:PhaseMassDensities] + if rho isa Pair + rho = last(rho) + end relperm = model.secondary_variables[:RelativePermeabilities] + if relperm isa Pair + relperm = last(relperm) + end + + pvt = rho.pvt reg = Int[pvtnum] function density_f(p, z, ph) pvt_i = pvt[ph] diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index fd744ba0..a1328dca 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -58,6 +58,7 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans end msg("Parsing physics and system.") sys, pvt = parse_physics_types(datafile, pvt_region = 1) + flow_sys = flow_system(sys) is_blackoil = sys isa StandardBlackOilSystem is_compositional = sys isa CompositionalSystem msg("Parsing reservoir domain.") @@ -66,7 +67,7 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans has_pvt = isnothing(pvt_reg) # Parse wells msg("Parsing schedule.") - wells, controls, limits, cstep, dt, well_forces = parse_schedule(domain, sys, datafile; simple_well = simple_well) + wells, controls, limits, cstep, dt, well_forces = parse_schedule(domain, flow_sys, datafile; simple_well = simple_well) msg("Setting up model with $(length(wells)) wells.") wells_pvt = Dict() wells_systems = [] @@ -659,6 +660,9 @@ function parse_physics_types(datafile; pvt_region = missing) end sys = pick_system_from_pvt(pvt, rhoS, phases, is_immiscible) end + if has("THERMAL") + sys = reservoir_system(flow = sys, thermal = ThermalSystem(sys)) + end return (system = sys, pvt = pvt) end From a3c71d5b0f5f518dc9813272e1fd0296739f4ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 09:44:26 +0100 Subject: [PATCH 38/92] A few thermal fixes --- src/input_simulation/data_input.jl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index a1328dca..c22e257d 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -59,8 +59,8 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans msg("Parsing physics and system.") sys, pvt = parse_physics_types(datafile, pvt_region = 1) flow_sys = flow_system(sys) - is_blackoil = sys isa StandardBlackOilSystem - is_compositional = sys isa CompositionalSystem + is_blackoil = flow_sys isa StandardBlackOilSystem + is_compositional = flow_sys isa CompositionalSystem msg("Parsing reservoir domain.") domain = parse_reservoir(datafile) pvt_reg = reservoir_regions(domain, :pvtnum) @@ -82,11 +82,18 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans wells_pvt[w.name] = pvt_w push!(wells_systems, sys_w) end + function wrap_flow_variable(x) + if sys isa CompositeSystem + x = Pair(:flow, x) + end + return x + end model = setup_reservoir_model(domain, sys; wells = wells, extra_out = false, wells_systems = wells_systems, kwarg...) for (k, submodel) in pairs(model.models) - if submodel.system isa MultiPhaseSystem + if model_or_domain_is_well(submodel) || k == :Reservoir # Modify secondary variables + @info "==" k submodel if !is_compositional svar = submodel.secondary_variables pvt_reg_i = reservoir_regions(submodel.data_domain, :pvtnum) @@ -98,12 +105,16 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans pvt_i = tuple(pvt_i...) rho = DeckPhaseMassDensities(pvt_i, regions = pvt_reg_i) if sys isa StandardBlackOilSystem + b_i = DeckShrinkageFactors(pvt_i, regions = pvt_reg_i) set_secondary_variables!(submodel, - ShrinkageFactors = DeckShrinkageFactors(pvt_i, regions = pvt_reg_i) + ShrinkageFactors = wrap_flow_variable(b_i) ) end mu = DeckPhaseViscosities(pvt_i, regions = pvt_reg_i) - set_secondary_variables!(submodel, PhaseViscosities = mu, PhaseMassDensities = rho) + set_secondary_variables!(submodel, + PhaseViscosities = wrap_flow_variable(mu), + PhaseMassDensities = wrap_flow_variable(rho) + ) end if k == :Reservoir rs = datafile["RUNSPEC"] From a621e84836547df278a49ee7872958edaf2cfce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 09:44:36 +0100 Subject: [PATCH 39/92] Update controls.jl --- src/facility/controls.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/facility/controls.jl b/src/facility/controls.jl index b75ebb5a..99273471 100644 --- a/src/facility/controls.jl +++ b/src/facility/controls.jl @@ -39,7 +39,11 @@ function Jutul.update_before_step_multimodel!(storage_g, model_g::MultiModel, mo pos = get_well_position(model.domain, key) q_t[pos] = valid_surface_rate_for_control(q_t[pos], newctrl) if changed - cfg.limits[key] = merge(cfg.limits[key], as_limit(newctrl.target)) + if isnothing(cfg.limits[key]) + cfg.limits[key] = as_limit(newctrl.target) + else + cfg.limits[key] = merge(cfg.limits[key], as_limit(newctrl.target)) + end end end cfg.step_index = current_step From 5a6da3958004ddc58a5adc5cd6b9ac48d137d3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 09:50:56 +0100 Subject: [PATCH 40/92] Thermal setup --- src/init/init.jl | 16 ++++++++++++++++ src/input_simulation/data_input.jl | 2 -- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 1ca60fcf..25d3130e 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -58,6 +58,7 @@ function equilibriate_state!(init, depths, model, sys, contacts, depth, datum_pr cells = 1:length(depths), rs = missing, rv = missing, + T_z = missing, s_min = missing, contacts_pc = missing, pvtnum = 1, @@ -139,6 +140,9 @@ function equilibriate_state!(init, depths, model, sys, contacts, depth, datum_pr init[:Pressure] = p init[:Saturations] = ones(1, length(p)) end + if !ismissing(T_z) + init[:Temperature] = T_z.(depths) + end return init end @@ -211,6 +215,17 @@ function parse_state0_equil(model, datafile) npvt = GeoEnergyIO.InputParser.number_of_tables(datafile, :pvtnum) nsat = GeoEnergyIO.InputParser.number_of_tables(datafile, :satnum) + if haskey(sol, "RTEMP") + Ti = only(sol["RTEMP"]) + T_z = z -> Ti + elseif haskey(sol, "TEMPVD") + z = vec(sol["TEMPVD"][:, 1]) + Tvd = vec(sol["TEMPVD"][:, 2] + 273.15) + T_z = get_1d_interpolator(z, Tvd) + else + T_z = missing + end + @assert length(equil) == nequil inits = [] inits_cells = [] @@ -342,6 +357,7 @@ function parse_state0_equil(model, datafile) contacts_pc = contacts_pc, s_min = s_min, s_max = s_max, + T_z = T_z, rs = rs, rv = rv, pc = pc diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index c22e257d..1fd5e336 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -93,7 +93,6 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans for (k, submodel) in pairs(model.models) if model_or_domain_is_well(submodel) || k == :Reservoir # Modify secondary variables - @info "==" k submodel if !is_compositional svar = submodel.secondary_variables pvt_reg_i = reservoir_regions(submodel.data_domain, :pvtnum) @@ -339,7 +338,6 @@ function parse_state0(model, datafile) state0 = setup_reservoir_state(model, init) end - function parse_state0_direct_assignment(model, datafile) sys = model.system init = Dict{Symbol, Any}() From d6abf6832cec37e2f8a103080957dcdbc9df7896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 10:03:03 +0100 Subject: [PATCH 41/92] Don't use saturations as primary for single phase --- src/multiphase.jl | 9 +++++---- src/variables/variables.jl | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/multiphase.jl b/src/multiphase.jl index 364733f6..c55008c8 100644 --- a/src/multiphase.jl +++ b/src/multiphase.jl @@ -115,8 +115,7 @@ function Saturations(;ds_max = 0.2) end function default_value(model, ::Saturations) - fsys = flow_system(model.system) - nph = number_of_phases(fsys) + nph = number_of_phases(model.system) return 1.0/nph end @@ -294,9 +293,11 @@ function select_primary_variables!(S, ::SinglePhaseSystem, model::SimulationMode S[:Pressure] = Pressure() end -function select_primary_variables!(S, ::ImmiscibleSystem, model::SimulationModel) +function select_primary_variables!(S, sys::ImmiscibleSystem, model::SimulationModel) S[:Pressure] = Pressure() - S[:Saturations] = Saturations() + if number_of_phases(sys) > 1 + S[:Saturations] = Saturations() + end end function select_equations!(eqs, sys::MultiPhaseSystem, model::SimulationModel) diff --git a/src/variables/variables.jl b/src/variables/variables.jl index c2c5441d..6234ef94 100644 --- a/src/variables/variables.jl +++ b/src/variables/variables.jl @@ -49,11 +49,17 @@ function select_default_darcy_parameters!(prm, domain, system::ImmiscibleSystem, add_connate_water_if_aqueous_present!(prm, domain, system) prm[:PhaseViscosities] = PhaseViscosities() prm[:FluidVolume] = FluidVolume() + if number_of_phases(system) == 1 + prm[:Saturations] = Saturations() + end end function select_default_darcy_parameters!(prm, domain, system::MultiPhaseSystem, formulation) add_connate_water_if_aqueous_present!(prm, domain, system) prm[:FluidVolume] = FluidVolume() + if number_of_phases(system) == 1 + prm[:Saturations] = Saturations() + end end function add_connate_water_if_aqueous_present!(prm, domain, system) From c330367e4879508b4fcb0411def00601be24bed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 10:08:52 +0100 Subject: [PATCH 42/92] Update mrst_input.jl --- src/input_simulation/mrst_input.jl | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index 6143d4f0..28bb42be 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -707,23 +707,26 @@ function model_from_mat_deck(G, data_domain, mrst_data, res_context) end function set_deck_specialization!(model, props, satnum, oil, water, gas) + sys = model.system svar = model.secondary_variables param = model.parameters - set_deck_relperm!(svar, param, props; oil = oil, water = water, gas = gas, satnum = satnum) - set_deck_pc!(svar, props; oil = oil, water = water, gas = gas, satnum = satnum) - set_deck_pvmult!(svar, param, props) + if number_of_phases(sys) > 1 + set_deck_relperm!(svar, param, sys, props; oil = oil, water = water, gas = gas, satnum = satnum) + set_deck_pc!(svar, param, sys, props; oil = oil, water = water, gas = gas, satnum = satnum) + end + set_deck_pvmult!(svar, param, sys, props) end -function set_deck_pc!(vars, props; kwarg...) +function set_deck_pc!(vars, param, sys, props; kwarg...) pc = deck_pc(props; kwarg...) if !isnothing(pc) - vars[:CapillaryPressure] = pc + vars[:CapillaryPressure] = wrap_reservoir_variable(sys, pc, :flow) end end -function set_deck_relperm!(vars, param, props; kwarg...) +function set_deck_relperm!(vars, param, sys, props; kwarg...) kr = deck_relperm(props; kwarg...) - vars[:RelativePermeabilities] = kr + vars[:RelativePermeabilities] = wrap_reservoir_variable(sys, kr, :flow) if scaling_type(kr) != NoKrScale ph = kr.phases @assert ph == :wog @@ -734,7 +737,7 @@ function set_deck_relperm!(vars, param, props; kwarg...) end end -function set_deck_pvmult!(vars, param, props) +function set_deck_pvmult!(vars, param, sys, props) # Rock compressibility (if present) if haskey(props, "ROCK") rock = JutulDarcy.flat_region_expand(props["ROCK"]) @@ -746,11 +749,20 @@ function set_deck_pvmult!(vars, param, props) static = param[:FluidVolume] delete!(param, :FluidVolume) param[:StaticFluidVolume] = static - vars[:FluidVolume] = LinearlyCompressiblePoreVolume(reference_pressure = rock[1], expansion = rock[2]) + ϕ = LinearlyCompressiblePoreVolume(reference_pressure = rock[1], expansion = rock[2]) + vars[:FluidVolume] = wrap_reservoir_variable(sys, ϕ, :flow) end end end +function wrap_reservoir_variable(sys::CompositeSystem, var::JutulVariables, type::Symbol = :flow) + return Pair(type, var) +end + +function wrap_reservoir_variable(sys, var, type::Symbol = :flow) + return var +end + function init_from_mat(mrst_data, model, param) state0 = mrst_data["state0"] p0 = state0["pressure"] From 2aa18a6fedb21d92fd00fa5a582fa3d1fe7bcf15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 10:49:39 +0100 Subject: [PATCH 43/92] More composite fixes --- src/input_simulation/data_input.jl | 12 +++--------- src/utils.jl | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 1fd5e336..b8522d04 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -82,12 +82,6 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans wells_pvt[w.name] = pvt_w push!(wells_systems, sys_w) end - function wrap_flow_variable(x) - if sys isa CompositeSystem - x = Pair(:flow, x) - end - return x - end model = setup_reservoir_model(domain, sys; wells = wells, extra_out = false, wells_systems = wells_systems, kwarg...) for (k, submodel) in pairs(model.models) @@ -106,13 +100,13 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans if sys isa StandardBlackOilSystem b_i = DeckShrinkageFactors(pvt_i, regions = pvt_reg_i) set_secondary_variables!(submodel, - ShrinkageFactors = wrap_flow_variable(b_i) + ShrinkageFactors = wrap_reservoir_variable(sys, b_i, :flow) ) end mu = DeckPhaseViscosities(pvt_i, regions = pvt_reg_i) set_secondary_variables!(submodel, - PhaseViscosities = wrap_flow_variable(mu), - PhaseMassDensities = wrap_flow_variable(rho) + PhaseViscosities = wrap_reservoir_variable(sys, mu, :flow), + PhaseMassDensities = wrap_reservoir_variable(sys, rho, :flow) ) end if k == :Reservoir diff --git a/src/utils.jl b/src/utils.jl index 15a30b4f..58eded16 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -762,6 +762,23 @@ function setup_reservoir_forces(model::MultiModel; control = nothing, limits = n end out = setup_forces(model; pairs(new_forces)..., kwarg...) end + # If the model is a composite model we need to do some extra work to pass on + # flow forces with the correct label. + # + # TODO: At the moment we have no mechanism for setting up forces for thermal + # specifically. + for (k, m) in pairs(submodels) + f = out[k] + if m isa Jutul.CompositeModel + mkeys = keys(m.system.systems) + tmp = Dict{Symbol, Any}() + tmp[:flow] = f + for mk in mkeys + tmp[mk] = nothing + end + out[k] = (; pairs(tmp)...) + end + end return out end From 0f68a81c3d3cdc9857bc59830650ae2a85a2b431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 14:12:14 +0100 Subject: [PATCH 44/92] Thermal fixes --- src/init/init.jl | 2 +- src/input_simulation/mrst_input.jl | 8 ++++++++ src/thermal/thermal.jl | 5 +++++ test/thermal.jl | 13 ++++--------- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 25d3130e..de24320f 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -189,7 +189,7 @@ function parse_state0_equil(model, datafile) end if has_water - kr_var = model.secondary_variables[:RelativePermeabilities] + kr_var = unwrap_reservoir_variable(model.secondary_variables[:RelativePermeabilities]) krw_fn = kr_var.krw if has_sat_reg @assert !isnothing(kr_var.regions) diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index 28bb42be..8b445120 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -763,6 +763,14 @@ function wrap_reservoir_variable(sys, var, type::Symbol = :flow) return var end +function unwrap_reservoir_variable(var) + return var +end + +function unwrap_reservoir_variable(var::Pair) + return last(var) +end + function init_from_mat(mrst_data, model, param) state0 = mrst_data["state0"] p0 = state0["pressure"] diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 59665073..da2be23c 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -73,6 +73,11 @@ Jutul.default_value(model, ::FluidHeatCapacity) = 5000.0 struct FluidInternalEnergy <: PhaseVariables end struct FluidEnthalpy <: PhaseVariables end +""" + FluidThermalConductivities() + +Variable defining the fluid component conductivity. +""" struct FluidThermalConductivities <: VectorVariables end Jutul.variable_scale(::FluidThermalConductivities) = 1e-10 Jutul.minimum_value(::FluidThermalConductivities) = 0.0 diff --git a/test/thermal.jl b/test/thermal.jl index 8b77cef3..0ea5f334 100644 --- a/test/thermal.jl +++ b/test/thermal.jl @@ -26,7 +26,7 @@ function solve_thermal(; # Define system and realize on grid sys_f = ImmiscibleSystem((LiquidPhase(), VaporPhase())) - sys_t = ThermalSystem(nphases = 2) + sys_t = ThermalSystem(sys_f) sys = reservoir_system(flow = sys_f, thermal = sys_t) D = discretized_domain_tpfv_flow(G) @@ -40,7 +40,7 @@ function solve_thermal(; model = SimulationModel(D, sys, data_domain = G, context = ctx) push!(model.output_variables, :Temperature) kr = BrooksCoreyRelativePermeabilities(2, [2.0, 2.0]) - replace_variables!(model, RelativePermeabilities = Pair(:flow, kr)) + replace_variables!(model, RelativePermeabilities = JutulDarcy.wrap_reservoir_variable(sys, kr)) forces_f = nothing forces = setup_forces(model, flow = forces_f) @@ -109,7 +109,7 @@ function solve_thermal_wells(; i_mix = [0.0, 1.0] c = [1e-6/bar, 1e-5/bar] end - sys_t = ThermalSystem(nphases = nph) + sys_t = ThermalSystem(sys_f) if composite if thermal sys = reservoir_system(flow = sys_f, thermal = sys_t) @@ -127,12 +127,7 @@ function solve_thermal_wells(; model, parameters = setup_reservoir_model(res, sys, wells = wells, block_backend = block_backend) # Replace the density function with our custom version for wells and reservoir ρ = ConstantCompressibilityDensities(p_ref = 1*bar, density_ref = rhoS, compressibility = c) - if composite - tmp = Pair(:flow, ρ) - else - tmp = ρ - end - replace_variables!(model, PhaseMassDensities = tmp) + replace_variables!(model, PhaseMassDensities = JutulDarcy.wrap_reservoir_variable(sys, ρ)) dt = repeat([30.0]*day, 12*5) rate_target = TotalRateTarget(sum(parameters[:Reservoir][:FluidVolume])/sum(dt)) bhp_target = BottomHolePressureTarget(50*bar) From 9d53d75dcecba219ef96867975db017b3aa51b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 20:38:57 +0100 Subject: [PATCH 45/92] Handle initialization of thermal conductivities --- src/input_simulation/data_input.jl | 20 ++++++++++++++++++++ src/thermal/thermal.jl | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index b8522d04..8939ee3e 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -526,6 +526,26 @@ function parse_reservoir(data_file) end extra_data_arg[:temperature] = temperature end + if haskey(grid, "THCROCK") + extra_data_arg[:rock_thermal_conductivity] = grid["THCROCK"][active_ix] + end + has(name) = haskey(data_file["RUNSPEC"], name) && data_file["RUNSPEC"][name] + + if has("THERMAL") + w = has("WATER") + o = has("OIL") + g = has("GAS") + fluid_conductivity = zeros(w+o+g, nc) + pos = 1 + for phase in ("WATER", "OIL", "GAS") + if has(phase) + fluid_conductivity[pos, :] .= grid["THC$phase"][active_ix] + pos += 1 + end + end + extra_data_arg[:fluid_thermal_conductivity] = fluid_conductivity + end + satnum = GeoEnergyIO.InputParser.get_data_file_cell_region(data_file, :satnum, active = active_ix) pvtnum = GeoEnergyIO.InputParser.get_data_file_cell_region(data_file, :pvtnum, active = active_ix) eqlnum = GeoEnergyIO.InputParser.get_data_file_cell_region(data_file, :eqlnum, active = active_ix) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index da2be23c..074275dc 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -98,7 +98,7 @@ function Jutul.default_parameter_values(data_domain, model, param::FluidThermalC nf = number_of_faces(data_domain) T = zeros(nph, nf) for ph in 1:nph - T[i, :] = compute_face_trans(data_domain, C[ph, :]) + T[ph, :] = compute_face_trans(data_domain, C[ph, :]) end end else From 677ee35ff87d6c575512283d8457c9dbc43a7396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 21:25:02 +0100 Subject: [PATCH 46/92] Transfer over some more thermal properties --- src/input_simulation/data_input.jl | 14 ++++++--- src/input_simulation/mrst_input.jl | 49 ++++++++++++++++++++++++++++++ src/thermal/thermal.jl | 27 ++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 8939ee3e..ab860346 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -61,6 +61,13 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans flow_sys = flow_system(sys) is_blackoil = flow_sys isa StandardBlackOilSystem is_compositional = flow_sys isa CompositionalSystem + is_thermal = sys isa CompositeSystem && haskey(sys.systems, :thermal) + + rs = datafile["RUNSPEC"] + oil = haskey(rs, "OIL") + water = haskey(rs, "WATER") + gas = haskey(rs, "GAS") + msg("Parsing reservoir domain.") domain = parse_reservoir(datafile) pvt_reg = reservoir_regions(domain, :pvtnum) @@ -109,11 +116,10 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans PhaseMassDensities = wrap_reservoir_variable(sys, rho, :flow) ) end + if is_thermal + set_thermal_deck_specialization!(submodel, datafile["PROPS"], domain[:pvtnum], oil, water, gas) + end if k == :Reservoir - rs = datafile["RUNSPEC"] - oil = haskey(rs, "OIL") - water = haskey(rs, "WATER") - gas = haskey(rs, "GAS") set_deck_specialization!(submodel, datafile["PROPS"], domain[:satnum], oil, water, gas) end end diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index 8b445120..558962a1 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -717,6 +717,55 @@ function set_deck_specialization!(model, props, satnum, oil, water, gas) set_deck_pvmult!(svar, param, sys, props) end +function set_thermal_deck_specialization!(model, props, pvtnum, oil, water, gas) + # SPECHEAT - fluid heat capacity F(T) + # SPECROCK - rock heat capacity by volume F(T) + if haskey(props, "SPECHEAT") + ix = Int[] + if water + push!(ix, 2) + end + if oil + push!(ix, 1) + end + if gas + push!(ix, 3) + end + tab = [] + for (i, specheat) in enumerate(props["SPECHEAT"]) + T = specheat[:, 1] .+ 273.15 + C_f = specheat[:, ix .+ 1] + + N = length(ix) + F = SVector{N, Float64}[] + for i in axes(C_f, 1) + push!(F, SVector{N, Float64}(C_f[i, :]...)) + end + push!(tab, get_1d_interpolator(T, F)) + end + tab = tuple(tab...) + v = TemperatureDependentVariable(tab, regions = pvtnum) + v = wrap_reservoir_variable(model.system, v, :thermal) + set_secondary_variables!(model, FluidHeatCapacity = v) + end + + if !model_or_domain_is_well(model) && haskey(props, "SPECROCK") + rock_density = first(model.data_domain[:rock_density]) + tab = [] + for (i, specrock) in enumerate(props["SPECROCK"]) + T = specrock[:, 1] .+ 273.15 + # (1 / volume) / (mass / volume) = 1 / mass... Input file does not + # know rock density. + C_r = specrock[:, 2] ./ rock_density + push!(tab, get_1d_interpolator(T, C_r)) + end + tab = tuple(tab...) + v = TemperatureDependentVariable(tab, regions = pvtnum) + v = wrap_reservoir_variable(model.system, v, :thermal) + set_secondary_variables!(model, RockHeatCapacity = v) + end +end + function set_deck_pc!(vars, param, sys, props; kwarg...) pc = deck_pc(props; kwarg...) if !isnothing(pc) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 074275dc..ce7b679e 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -73,6 +73,33 @@ Jutul.default_value(model, ::FluidHeatCapacity) = 5000.0 struct FluidInternalEnergy <: PhaseVariables end struct FluidEnthalpy <: PhaseVariables end +struct TemperatureDependentVariable{T, R, N} <: VectorVariables + tab::T + regions::R + function TemperatureDependentVariable(tab; regions = nothing) + tab = region_wrap(tab, regions) + ex = first(tab) + N = length(ex(273.15 + 30.0)) + new{typeof(tab), typeof(regions), N}(tab, regions) + end +end + +function Jutul.values_per_entity(model, ::TemperatureDependentVariable{T, R, N}) where {T, R, N} + return N +end + +@jutul_secondary function update_temperature_dependent!(result, var::TemperatureDependentVariable{T, R, N}, model, Temperature, ix) where {T, R, N} + for c in ix + reg = region(var.regions, c) + interpolator = table_by_region(var.tab, reg) + F_of_T = interpolator(Temperature[c]) + for i in 1:N + result[i, c] = F_of_T[i] + end + end + return result +end + """ FluidThermalConductivities() From 87e8b19c14a2fd8fae873c0a8e12e9562028d1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 20 Feb 2024 22:01:20 +0100 Subject: [PATCH 47/92] Temperature fix --- src/init/init.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init/init.jl b/src/init/init.jl index de24320f..76e0f573 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -216,7 +216,7 @@ function parse_state0_equil(model, datafile) nsat = GeoEnergyIO.InputParser.number_of_tables(datafile, :satnum) if haskey(sol, "RTEMP") - Ti = only(sol["RTEMP"]) + Ti = convert_to_si(only(sol["RTEMP"]), :Celsius) T_z = z -> Ti elseif haskey(sol, "TEMPVD") z = vec(sol["TEMPVD"][:, 1]) From 435af17074d025cf215c83c3336dead002b1ef8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 21 Feb 2024 15:33:56 +0100 Subject: [PATCH 48/92] Add subcrossterm to thermal --- src/facility/cross_terms.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/facility/cross_terms.jl b/src/facility/cross_terms.jl index 408af65d..07346b2b 100644 --- a/src/facility/cross_terms.jl +++ b/src/facility/cross_terms.jl @@ -284,6 +284,15 @@ function Base.show(io::IO, d::ReservoirFromWellThermalCT) n = length(d.CI) print(io, "ReservoirFromWellThermalCT ($n connections)") end + +function Jutul.subcrossterm(ct::ReservoirFromWellThermalCT, ctp, m_t, m_s, map_res::FiniteVolumeGlobalMap, ::TrivialGlobalMap, partition) + (; CI, WI, reservoir_cells, well_cells) = ct + rc = map( + c -> Jutul.local_cell(c, map_res), + reservoir_cells) + return ReservoirFromWellThermalCT(copy(CI), copy(WI), rc, copy(well_cells)) +end + struct WellFromFacilityThermalCT <: Jutul.AdditiveCrossTerm well::Symbol end From a1a463064b8ae1c3ee27ed2b0de5c762da01b418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 10:31:15 +0100 Subject: [PATCH 49/92] Allow passing of custom config to simulate_reservoir --- src/utils.jl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 58eded16..aa02c116 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -445,18 +445,25 @@ function simulate_reservoir(state0, model, dt; parameters = setup_parameters(model), restart = false, forces = setup_forces(model), + config = missing, kwarg... ) - sim, config = setup_reservoir_simulator(model, state0, parameters; kwarg...) + sim, config_new = setup_reservoir_simulator(model, state0, parameters; kwarg...) + if ismissing(config) + config = config_new + end result = simulate!(sim, dt, forces = forces, config = config, restart = restart); - return ReservoirSimResult(model, result, forces) + return ReservoirSimResult(model, result, forces; simulator = sim, config = config) end function simulate_reservoir(case::JutulCase; restart = false, kwarg...) (; model, forces, state0, parameters, dt) = case - sim, config = setup_reservoir_simulator(model, state0, parameters; kwarg...) + sim, config_new = setup_reservoir_simulator(model, state0, parameters; kwarg...) + if ismissing(config) + config = config_new + end result = simulate!(sim, dt, forces = forces, config = config, restart = restart); - return ReservoirSimResult(model, result, forces) + return ReservoirSimResult(model, result, forces; simulator = sim, config = config) end function set_default_cnv_mb!(cfg::JutulConfig, sim::JutulSimulator; kwarg...) From d7c213fefb5a7c633d1d4e52e2d035c99ac58b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 13:09:58 +0100 Subject: [PATCH 50/92] add PT table variable --- src/thermal/thermal.jl | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index ce7b679e..155b2621 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -100,6 +100,33 @@ end return result end +struct PressureTemperatureDependentVariable{T, R, N} <: VectorVariables + tab::T + regions::R + function PressureTemperatureDependentVariable(tab; regions = nothing) + tab = region_wrap(tab, regions) + ex = first(tab) + N = length(ex(1e8, 273.15 + 30.0)) + new{typeof(tab), typeof(regions), N}(tab, regions) + end +end + +function Jutul.values_per_entity(model, ::PressureTemperatureDependentVariable{T, R, N}) where {T, R, N} + return N +end + +@jutul_secondary function update_temperature_dependent!(result, var::PressureTemperatureDependentVariable{T, R, N}, model, Pressure, Temperature, ix) where {T, R, N} + for c in ix + reg = region(var.regions, c) + interpolator = table_by_region(var.tab, reg) + F_of_T = interpolator(Pressure[c], Temperature[c]) + for i in 1:N + result[i, c] = F_of_T[i] + end + end + return result +end + """ FluidThermalConductivities() From ff9490ca32c6622a671d12e03196ef5790da27a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 13:32:16 +0100 Subject: [PATCH 51/92] Add heat capacities to reservoir_domain --- src/utils.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index aa02c116..fad419b0 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -33,16 +33,24 @@ represents a compact full tensor representation (6 elements in 3D, 3 in 2D). |------------------------------|------------------------------------------|--------------|---------| | `permeability` | Rock ability to conduct fluid flow | ``m^2`` | 100 mD | | `porosity` | Rock void fraction open to flow (0 to 1) | - | 0.3 | +| `rock_density` | Mass density of rock | ``kg^3/m^3`` | 2000.0 | +| `rock_heat_capacity` | Specific heat capacity of rock | ``J/(kg K)`` | 900.0 | | `rock_thermal_conductivity` | Heat conductivity of rock | ``W/m K`` | 3.0 | | `fluid_thermal_conductivity` | Heat conductivity of fluid phases | ``W/m K`` | 0.6 | -| `rock_density` | Mass density of rock | ``kg^3/m^3`` | 2000.0 | +| `fluid_heat_capacity` | Specific heat capacity of fluid phases | ``J/(kg K)`` | 4184.0 | +Note that the default values are taken to be roughly those of water for fluid +phases and sandstone for those of rock. Choice of values can severely impact +your simulation results - take care to check the values that your physical +system makes use of! """ function reservoir_domain(g; permeability = convert_to_si(0.1, :darcy), porosity = 0.1, rock_thermal_conductivity = 3.0, # W/m K (~sandstone) fluid_thermal_conductivity = 0.6, # W/m K (~water) + rock_heat_capacity = 900.0, # ~sandstone + fluid_heat_capacity = 4184.0, # ~water rock_density = 2000.0, diffusion = missing, kwarg... From b2b72dbc157898bae7432e96e4f5d42d686a3ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 13:40:52 +0100 Subject: [PATCH 52/92] Set default maximum temperature to 1 million degrees K --- src/variables/pvt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variables/pvt.jl b/src/variables/pvt.jl index cf45c18f..ab715ece 100644 --- a/src/variables/pvt.jl +++ b/src/variables/pvt.jl @@ -153,7 +153,7 @@ end Base.@kwdef struct Temperature{T} <: ScalarVariable min::T = 0.0 - max::T = Inf + max::T = 1e6 max_rel::Union{T, Nothing} = nothing max_abs::Union{T, Nothing} = nothing end From 497250839532a72d948303d82a8ad0db11d23f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 13:55:10 +0100 Subject: [PATCH 53/92] Use new thermal properties for default values --- src/thermal/thermal.jl | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 155b2621..62bf39a2 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -52,6 +52,17 @@ end struct RockHeatCapacity <: ScalarVariable end Jutul.default_value(model, ::RockHeatCapacity) = 1000.0 + +function Jutul.default_parameter_values(data_domain, model, param::RockHeatCapacity, symb) + if haskey(data_domain, :rock_heat_capacity, Cells()) + # This takes precedence + T = copy(data_domain[:rock_heat_capacity]) + else + T = fill(default_value(model, param), number_of_cells(data_domain)) + end + return T +end + struct RockDensity <: ScalarVariable end Jutul.default_value(model, ::RockDensity) = 2000.0 @@ -69,7 +80,22 @@ struct RockInternalEnergy <: ScalarVariable end struct TotalThermalEnergy <: ScalarVariable end struct FluidHeatCapacity <: PhaseVariables end -Jutul.default_value(model, ::FluidHeatCapacity) = 5000.0 +Jutul.default_value(model, ::FluidHeatCapacity) = 4184.0 + +function Jutul.default_parameter_values(data_domain, model, param::FluidHeatCapacity, symb) + nph = number_of_phases(model.system) + if haskey(data_domain, :fluid_heat_capacity, Cells()) + # This takes precedence + T = copy(data_domain[:fluid_heat_capacity]) + if T isa Vector + T = repeat(T', nph, 1) + end + else + T = fill(default_value(model, param), nph, number_of_cells(data_domain)) + end + return T +end + struct FluidInternalEnergy <: PhaseVariables end struct FluidEnthalpy <: PhaseVariables end From ca0f60980618b2940eecff6ebd6a32adb7e14756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 14:09:15 +0100 Subject: [PATCH 54/92] Convergence criterion for energy --- src/thermal/equations.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/thermal/equations.jl b/src/thermal/equations.jl index 76f287c3..69c09696 100644 --- a/src/thermal/equations.jl +++ b/src/thermal/equations.jl @@ -41,3 +41,11 @@ function thermal_heat_flux(face, state, model, grad, upw, flux_type) conductive_flux = -λ_total*gradient(T, grad) return conductive_flux + convective_flux end + + +function Jutul.convergence_criterion(model, storage, eq::ConservationLaw{:TotalThermalEnergy}, eq_s, r; dt = 1.0, update_report = missing) + a = active_entities(model.domain, Cells()) + E0 = storage.state0.TotalThermalEnergy + @tullio max e := abs(r[i]) * dt / value(E0[a[i]]) + return (Max = (errors = (e, ), names = ("Energy balance",)), ) +end From ac1c20a859cfef3ecf7449adca4b1a962350bf0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 14:09:31 +0100 Subject: [PATCH 55/92] Add missing kwarg --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index fad419b0..ae2d1156 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -464,7 +464,7 @@ function simulate_reservoir(state0, model, dt; return ReservoirSimResult(model, result, forces; simulator = sim, config = config) end -function simulate_reservoir(case::JutulCase; restart = false, kwarg...) +function simulate_reservoir(case::JutulCase; config = missing, restart = false, kwarg...) (; model, forces, state0, parameters, dt) = case sim, config_new = setup_reservoir_simulator(model, state0, parameters; kwarg...) if ismissing(config) From c418b81c9994bb8657f7c9b976490636af4ae1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 20:27:15 +0100 Subject: [PATCH 56/92] Fix phase_face_average signature --- src/flux.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flux.jl b/src/flux.jl index bf18e551..0c4faa1f 100644 --- a/src/flux.jl +++ b/src/flux.jl @@ -80,7 +80,7 @@ function face_average(F, tpfa) return 0.5*(F(tpfa.right) + F(tpfa.left)) end -function phase_face_average(phase_property, tpfa, cell) +function phase_face_average(phase_property, tpfa, phase) F(cell) = @inbounds phase_property[phase, cell] return face_average(F, tpfa) end From 77c9140a5e569b60c930266ef6c128ea7e8884d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 22 Feb 2024 20:57:31 +0100 Subject: [PATCH 57/92] Use face average for saturations in conductive flux --- src/thermal/equations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thermal/equations.jl b/src/thermal/equations.jl index 69c09696..6cd09a72 100644 --- a/src/thermal/equations.jl +++ b/src/thermal/equations.jl @@ -36,7 +36,7 @@ function thermal_heat_flux(face, state, model, grad, upw, flux_type) F_α = darcy_phase_mass_flux(face, α, state, model, flux_type, grad, upw, flow_common) H_face_α = phase_upwind(upw, H_f, α, F_α) convective_flux += H_face_α*F_α - λ_total += λ_f[α, face]*phase_upwind(upw, S, α, F_α) + λ_total += λ_f[α, face]*phase_face_average(S, grad, α) end conductive_flux = -λ_total*gradient(T, grad) return conductive_flux + convective_flux From 525b7b2ed1452272eaf7b2b8d42df4bbbba26e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 23 Feb 2024 09:42:37 +0100 Subject: [PATCH 58/92] Use analytical K-value flash when ncomp = 2 or 3 --- src/multicomponent/variables/flash.jl | 34 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/multicomponent/variables/flash.jl b/src/multicomponent/variables/flash.jl index 7fc1ae7a..7699f827 100644 --- a/src/multicomponent/variables/flash.jl +++ b/src/multicomponent/variables/flash.jl @@ -258,20 +258,26 @@ end function k_value_flash!(result::FR, eos, P, T, Z, z) where FR Num_t = Base.promote_type(typeof(P), typeof(T), eltype(Z)) - @. z = max(value(Z), 1e-8) - # Conditions - c = (p = value(P), T = value(T), z = z) + ncomp = length(z) c_ad = (p = P, T = T, z = Z) K_ad = eos.K_values_evaluator(c_ad) - K = result.K x = result.liquid.mole_fractions y = result.vapor.mole_fractions - ncomp = length(z) - - @inbounds for i in 1:ncomp - K[i] = value(K_ad[i]) + analytical_rr = ncomp == 2 || ncomp == 3 + K = result.K + if analytical_rr + # If we have 2 or 3 components the Rachford-Rice equations have an + # analytical solution. We can then bypass a bunch of chain rule magic. + V = flash_2ph!(nothing, K_ad, eos, c_ad, analytical = true) + else + @. z = max(value(Z), 1e-8) + # Conditions + c_numeric = (p = value(P), T = value(T), z = z) + @inbounds for i in 1:ncomp + K[i] = value(K_ad[i]) + end + V = flash_2ph!(nothing, K, eos, c_numeric) end - V = flash_2ph!(nothing, K, eos, c) pure_liquid = V <= 0.0 pure_vapor = V >= 1.0 @@ -286,17 +292,21 @@ function k_value_flash!(result::FR, eos, P, T, Z, z) where FR x[i] = Z_i y[i] = Z_i end - V = convert(Num_t, clamp(V, 0.0, 1.0)) + V = Num_t(pure_vapor) else phase_state = MultiComponentFlash.two_phase_lv - V = add_derivatives_to_vapor_fraction_rachford_rice(V, K_ad, Z, K, z) + if !analytical_rr + V = add_derivatives_to_vapor_fraction_rachford_rice(value(V), K_ad, Z, K, z) + end + V::Num_t @inbounds for i in 1:ncomp - K_i = K[i] + K_i = K_ad[i] x_i = liquid_mole_fraction(Z[i], K_i, V) x[i] = x_i y[i] = vapor_mole_fraction(x_i, K_i) end end + V::Num_t Z_L = Z_V = convert(Num_t, 1.0) new_result = FlashedMixture2Phase(phase_state, K, V, x, y, Z_L, Z_V) return new_result::FR From 02606fb1ea0b822eeba0cf988a582e9ba29009ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 23 Feb 2024 13:12:00 +0100 Subject: [PATCH 59/92] Fixes to welopen --- src/input_simulation/data_input.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index ab860346..c0fb02d5 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -1324,13 +1324,13 @@ function apply_welopen!(controls, compdat, wk, controls_if_active) end else cdat = compdat[name] - ijk = keys(cdat) - + ijk = collect(keys(cdat)) + nperf = length(ijk) first_num = max(first_num, 1) if last_num < 1 - last_num = length(ijk) + last_num = nperf end - for i in eachindex(ijk) + for i in 1:nperf if i < first_num continue elseif i > last_num @@ -1341,7 +1341,7 @@ function apply_welopen!(controls, compdat, wk, controls_if_active) is_match(ix, ix_i) = ix < 1 || ix_i == ix if is_match(I, I_i) && is_match(J, J_i) && is_match(K, K_i) c = OrderedDict{Symbol, Any}() - for (k, v) in current_cdat + for (k, v) in pairs(current_cdat) c[k] = v end c[:open] = is_open From ef8c1759c996cb79ff0005f08a59b26375baee00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 23 Feb 2024 14:03:37 +0100 Subject: [PATCH 60/92] Fix to PBVD init --- src/init/init.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init/init.jl b/src/init/init.jl index 76e0f573..1e77bda2 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -334,7 +334,7 @@ function parse_state0_equil(model, datafile) pbvd = sol["PBVD"][ereg] z = pbvd[:, 1] pb = vec(pbvd[:, 2]) - Rs = sys.rs_max.(pb) + Rs = sys.rs_max[preg].(pb) end rs = Jutul.LinearInterpolant(z, Rs_scale.*Rs) else From be3808e6b0a029199eb2d297606dd5589cbb6ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 23 Feb 2024 14:29:44 +0100 Subject: [PATCH 61/92] Update init.jl --- src/init/init.jl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 1e77bda2..92cb7a91 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -268,11 +268,16 @@ function parse_state0_equil(model, datafile) cap = cap[2:end] end ix = unique(i -> cap[i], 1:length(cap)) - if i == 1 && get_phases(model.system)[1] isa AqueousPhase @. cap *= -1 end - push!(pc, (s = s[ix], pc = cap[ix])) + s = s[ix] + cap = cap[ix] + if length(s) == 1 + push!(s, s[end]) + push!(cap, cap[end]+1.0) + end + push!(pc, (s = s, pc = cap)) end else pc = nothing @@ -547,9 +552,8 @@ function determine_saturations(depths, contacts, pressures; s_min = missing, s_m s, pc_pair = pc[offset] pc_max = maximum(pc_pair) pc_min = minimum(pc_pair) - - I = get_1d_interpolator(pc_pair, s) - I_pc = get_1d_interpolator(s, pc_pair) + I = get_1d_interpolator(pc_pair, s, constant_dx = false) + I_pc = get_1d_interpolator(s, pc_pair, constant_dx = false) for i in eachindex(depths) z = depths[i] From 095c1d96c5912cc882b7b181c35a232f0ea76ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 23 Feb 2024 14:56:11 +0100 Subject: [PATCH 62/92] Update init.jl --- src/init/init.jl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 92cb7a91..8c08d6ac 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -572,12 +572,23 @@ function determine_saturations(depths, contacts, pressures; s_min = missing, s_m offset += 1 end end + bad_cells = Int[] for i in eachindex(depths) - s_fill = 1 - sum(view(sat, :, i)) + sat_i = view(sat, :, i) + sat_tot = sum(sat_i) + s_fill = 1 - sat_tot if s_fill < 0 - @warn "Negative saturation in cell $i: $s_fill" + push!(bad_cells, i) + sat[ref_ix, i] = 0.0 + for j in axes(sat, 1) + sat[j, i] /= sat_tot + end + else + sat[ref_ix, i] = s_fill end - sat[ref_ix, i] = s_fill + end + if length(bad_cells) > 0 + @warn "Negative saturation in $(length(bad_cells)) cells for phase $ref_ix. Normalizing." end end return (sat, sat_pc) From 33fcd5caa058788a23f769ce5f89563b99aba244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 19:37:03 +0100 Subject: [PATCH 63/92] Put heat capacity per component instead of phase Needed to generalize to miscible cases --- src/facility/cross_terms.jl | 11 +++++------ src/input_simulation/mrst_input.jl | 2 +- src/thermal/thermal.jl | 20 +++++++++++--------- src/thermal/variables.jl | 4 ++-- src/types.jl | 2 +- src/utils.jl | 20 ++++++++++---------- src/variables/variables.jl | 4 ++-- test/thermal.jl | 2 +- 8 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/facility/cross_terms.jl b/src/facility/cross_terms.jl index 07346b2b..b43f2735 100644 --- a/src/facility/cross_terms.jl +++ b/src/facility/cross_terms.jl @@ -314,19 +314,18 @@ function update_cross_term_in_entity!(out, i, qT += 0*bottom_hole_pressure(state_well) cell = well_top_node() - H = well_top_node_enthalpy(ctrl, state_well, cell) + H = well_top_node_enthalpy(ctrl, well, state_well, cell) out[] = -qT*H end -function well_top_node_enthalpy(ctrl::InjectorControl, state_well, cell) - heat_capacity = state_well.FluidHeatCapacity[cell] +function well_top_node_enthalpy(ctrl::InjectorControl, model, state_well, cell) p = state_well.Pressure[cell] - density = ctrl.mixture_density + # density = ctrl.mixture_density T = ctrl.temperature nph = size(state_well.Saturations, 1) H = 0 for ph in 1:nph - C = state_well.FluidHeatCapacity[ph, cell] + C = state_well.ComponentHeatCapacity[ph, cell] dens = state_well.PhaseMassDensities[ph, cell] S = state_well.Saturations[ph, cell] H += S*(C*T + p/dens) @@ -334,7 +333,7 @@ function well_top_node_enthalpy(ctrl::InjectorControl, state_well, cell) return H end -function well_top_node_enthalpy(ctrl, state_well, cell) +function well_top_node_enthalpy(ctrl, model, state_well, cell) H = state_well.FluidEnthalpy S = state_well.Saturations H_w = 0.0 diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index 558962a1..8d686178 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -746,7 +746,7 @@ function set_thermal_deck_specialization!(model, props, pvtnum, oil, water, gas) tab = tuple(tab...) v = TemperatureDependentVariable(tab, regions = pvtnum) v = wrap_reservoir_variable(model.system, v, :thermal) - set_secondary_variables!(model, FluidHeatCapacity = v) + set_secondary_variables!(model, ComponentHeatCapacity = v) end if !model_or_domain_is_well(model) && haskey(props, "SPECROCK") diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 62bf39a2..c1fa5b5d 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -79,19 +79,21 @@ end struct RockInternalEnergy <: ScalarVariable end struct TotalThermalEnergy <: ScalarVariable end -struct FluidHeatCapacity <: PhaseVariables end -Jutul.default_value(model, ::FluidHeatCapacity) = 4184.0 +struct ComponentHeatCapacity <: ComponentVariabless end +Jutul.default_value(model, ::ComponentHeatCapacity) = 4184.0 -function Jutul.default_parameter_values(data_domain, model, param::FluidHeatCapacity, symb) - nph = number_of_phases(model.system) - if haskey(data_domain, :fluid_heat_capacity, Cells()) +function Jutul.default_parameter_values(data_domain, model, param::ComponentHeatCapacity, symb) + ncomp = number_of_components(model.system) + if haskey(data_domain, :component_heat_capacity, Cells()) # This takes precedence - T = copy(data_domain[:fluid_heat_capacity]) + T = copy(data_domain[:component_heat_capacity]) if T isa Vector - T = repeat(T', nph, 1) + T = repeat(T', ncomp, 1) + else + @assert size(T, 1) == ncomp end else - T = fill(default_value(model, param), nph, number_of_cells(data_domain)) + T = fill(default_value(model, param), ncomp, number_of_cells(data_domain)) end return T end @@ -222,7 +224,7 @@ function select_parameters!(S, system::ThermalSystem, model) S[:RockDensity] = RockDensity() S[:BulkVolume] = BulkVolume() # Fluid heat related parameters - S[:FluidHeatCapacity] = FluidHeatCapacity() + S[:ComponentHeatCapacity] = ComponentHeatCapacity() S[:FluidVolume] = FluidVolume() # Fluid flow related parameters S[:PhaseMassDensities] = ConstantCompressibilityDensities(nph) diff --git a/src/thermal/variables.jl b/src/thermal/variables.jl index 5929f27e..34b6a8ab 100644 --- a/src/thermal/variables.jl +++ b/src/thermal/variables.jl @@ -1,8 +1,8 @@ -@jutul_secondary function update_fluid_internal_energy!(U, fe::FluidInternalEnergy, model::ThermalModel, Temperature, FluidHeatCapacity, ix) +@jutul_secondary function update_fluid_internal_energy!(U, fe::FluidInternalEnergy, model::ThermalModel, Temperature, ComponentHeatCapacity, ix) for i in ix T = Temperature[i] for ph in axes(U, 1) - U[ph, i] = FluidHeatCapacity[ph, i]*T + U[ph, i] = ComponentHeatCapacity[ph, i]*T end end end diff --git a/src/types.jl b/src/types.jl index 7d4f6c43..081a98e0 100644 --- a/src/types.jl +++ b/src/types.jl @@ -17,7 +17,7 @@ const CompositionalModel = SimulationModel{D, S, F, C} where {D, S<:Compositiona abstract type BlackOilSystem <: MultiComponentSystem end abstract type PhaseVariables <: VectorVariables end -abstract type ComponentVariable <: VectorVariables end +abstract type ComponentVariables <: VectorVariables end struct MultiPhaseCompositionalSystemLV{E, T, O, R, N} <: CompositionalSystem where T<:Tuple phases::T diff --git a/src/utils.jl b/src/utils.jl index ae2d1156..4c4dbc7f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -29,15 +29,15 @@ represents a compact full tensor representation (6 elements in 3D, 3 in 2D). # Default data and their values -| Name | Explanation | Unit | Default | -|------------------------------|------------------------------------------|--------------|---------| -| `permeability` | Rock ability to conduct fluid flow | ``m^2`` | 100 mD | -| `porosity` | Rock void fraction open to flow (0 to 1) | - | 0.3 | -| `rock_density` | Mass density of rock | ``kg^3/m^3`` | 2000.0 | -| `rock_heat_capacity` | Specific heat capacity of rock | ``J/(kg K)`` | 900.0 | -| `rock_thermal_conductivity` | Heat conductivity of rock | ``W/m K`` | 3.0 | -| `fluid_thermal_conductivity` | Heat conductivity of fluid phases | ``W/m K`` | 0.6 | -| `fluid_heat_capacity` | Specific heat capacity of fluid phases | ``J/(kg K)`` | 4184.0 | +| Name | Explanation | Unit | Default | +|------------------------------|--------------------------------------------|--------------|---------| +| `permeability` | Rock ability to conduct fluid flow | ``m^2`` | 100 mD | +| `porosity` | Rock void fraction open to flow (0 to 1) | - | 0.3 | +| `rock_density` | Mass density of rock | ``kg^3/m^3`` | 2000.0 | +| `rock_heat_capacity` | Specific heat capacity of rock | ``J/(kg K)`` | 900.0 | +| `rock_thermal_conductivity` | Heat conductivity of rock | ``W/m K`` | 3.0 | +| `fluid_thermal_conductivity` | Heat conductivity of fluid phases | ``W/m K`` | 0.6 | +| `component_heat_capacity` | Specific heat capacity of fluid components | ``J/(kg K)`` | 4184.0 | Note that the default values are taken to be roughly those of water for fluid phases and sandstone for those of rock. Choice of values can severely impact @@ -50,7 +50,7 @@ function reservoir_domain(g; rock_thermal_conductivity = 3.0, # W/m K (~sandstone) fluid_thermal_conductivity = 0.6, # W/m K (~water) rock_heat_capacity = 900.0, # ~sandstone - fluid_heat_capacity = 4184.0, # ~water + component_heat_capacity = 4184.0, # ~water rock_density = 2000.0, diffusion = missing, kwarg... diff --git a/src/variables/variables.jl b/src/variables/variables.jl index 6234ef94..ebab523b 100644 --- a/src/variables/variables.jl +++ b/src/variables/variables.jl @@ -7,10 +7,10 @@ include("viscosity.jl") degrees_of_freedom_per_entity(model, sf::PhaseVariables) = number_of_phases(model.system) # Single-phase specialization -degrees_of_freedom_per_entity(model::SimulationModel{D, S}, sf::ComponentVariable) where {D, S<:SinglePhaseSystem} = 1 +degrees_of_freedom_per_entity(model::SimulationModel{D, S}, sf::ComponentVariables) where {D, S<:SinglePhaseSystem} = 1 # Immiscible specialization -degrees_of_freedom_per_entity(model::SimulationModel{D, S}, sf::ComponentVariable) where {D, S<:ImmiscibleSystem} = number_of_phases(model.system) +degrees_of_freedom_per_entity(model::SimulationModel{D, S}, sf::ComponentVariables) where {D, S<:ImmiscibleSystem} = number_of_phases(model.system) function select_secondary_variables!(S, system::MultiPhaseSystem, model) select_default_darcy_secondary_variables!(S, model.domain, system, model.formulation) diff --git a/test/thermal.jl b/test/thermal.jl index 0ea5f334..757191da 100644 --- a/test/thermal.jl +++ b/test/thermal.jl @@ -49,7 +49,7 @@ function solve_thermal(; RockThermalConductivities = 1e-2, FluidThermalConductivities = 1e-2, RockDensity = 1e3, - FluidHeatCapacity = 10000.0, + ComponentHeatCapacity = 10000.0, RockHeatCapacity = 500.0) T0 = repeat([273.15], nc) T0[1] = 500.0 From 936ff4aed0ff53eeb237eddd84c5d429658f3ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 20:09:54 +0100 Subject: [PATCH 64/92] Some thermal related improvements --- src/thermal/thermal.jl | 2 +- src/thermal/variables.jl | 5 +++-- src/variables/variables.jl | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index c1fa5b5d..9eea8715 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -79,7 +79,7 @@ end struct RockInternalEnergy <: ScalarVariable end struct TotalThermalEnergy <: ScalarVariable end -struct ComponentHeatCapacity <: ComponentVariabless end +struct ComponentHeatCapacity <: ComponentVariables end Jutul.default_value(model, ::ComponentHeatCapacity) = 4184.0 function Jutul.default_parameter_values(data_domain, model, param::ComponentHeatCapacity, symb) diff --git a/src/thermal/variables.jl b/src/thermal/variables.jl index 34b6a8ab..5b3fd090 100644 --- a/src/thermal/variables.jl +++ b/src/thermal/variables.jl @@ -1,8 +1,9 @@ @jutul_secondary function update_fluid_internal_energy!(U, fe::FluidInternalEnergy, model::ThermalModel, Temperature, ComponentHeatCapacity, ix) + @assert size(U) == size(ComponentHeatCapacity) "This fluid internal energy implementation assumes immiscible phases." for i in ix T = Temperature[i] - for ph in axes(U, 1) - U[ph, i] = ComponentHeatCapacity[ph, i]*T + for c in axes(U, 1) + U[c, i] = ComponentHeatCapacity[c, i]*T end end end diff --git a/src/variables/variables.jl b/src/variables/variables.jl index ebab523b..6ad4b973 100644 --- a/src/variables/variables.jl +++ b/src/variables/variables.jl @@ -6,6 +6,9 @@ include("viscosity.jl") degrees_of_freedom_per_entity(model, sf::PhaseVariables) = number_of_phases(model.system) +# Generic version +degrees_of_freedom_per_entity(model, sf::ComponentVariables) = number_of_components(model.system) + # Single-phase specialization degrees_of_freedom_per_entity(model::SimulationModel{D, S}, sf::ComponentVariables) where {D, S<:SinglePhaseSystem} = 1 From 114bb0d40dbcca18872b6c49ce7a2555d09fb0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 20:29:36 +0100 Subject: [PATCH 65/92] Make ThermalSystem hold the flow system --- src/thermal/thermal.jl | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 9eea8715..af2bc988 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -1,32 +1,25 @@ """ - ThermalSystem(number_of_phases = 1, number_of_components = number_of_phases, formulation = :Temperature) + ThermalSystem(flow_system, formulation = :Temperature) Geothermal system that defines heat transfer through fluid advection and through -the rock itself. Can be combined with a multiphase system using [`Jutul.CompositeSystem`](@ref). +the rock itself. Can be combined with a multiphase system using +[`Jutul.CompositeSystem`](@ref). """ -struct ThermalSystem{T} <: JutulSystem - nph::Int64 - ncomp::Int64 - function ThermalSystem(; - number_of_phases = 1, - number_of_components = number_of_phases, +struct ThermalSystem{T, F} <: JutulSystem + flow_system::F + function ThermalSystem(sys::T; formulation = :Temperature - ) + ) where T<:Union{MultiPhaseSystem, Missing} @assert formulation == :Temperature - new{formulation}(number_of_phases, number_of_components) + new{formulation, T}(sys) end end -function ThermalSystem(sys::MultiPhaseSystem; kwarg...) - nph = number_of_phases(sys) - nc = number_of_components(sys) - return ThermalSystem(number_of_phases = nph, number_of_components = nc; kwarg...) -end - thermal_system(sys::ThermalSystem) = sys thermal_system(sys::CompositeSystem) = sys.systems.thermal +flow_system(sys::ThermalSystem) = sys.flow_system const ThermalModel = SimulationModel{<:JutulDomain, <:ThermalSystem, <:Any, <:Any} @@ -210,8 +203,8 @@ function Jutul.default_parameter_values(data_domain, model, param::RockThermalCo return T end -number_of_phases(t::ThermalSystem) = t.nph -number_of_components(t::ThermalSystem) = t.ncomp +number_of_phases(t::ThermalSystem) = number_of_phases(flow_system(t)) +number_of_components(t::ThermalSystem) = number_of_components(flow_system(t)) function select_primary_variables!(S, system::ThermalSystem, model) S[:Temperature] = Temperature() From 54ba562ca86df99ba0aca9157037712193b2f596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 20:39:15 +0100 Subject: [PATCH 66/92] Add some dispatches for different thermal models --- src/thermal/thermal.jl | 4 ++++ src/thermal/variables.jl | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index af2bc988..0e92127b 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -23,6 +23,10 @@ flow_system(sys::ThermalSystem) = sys.flow_system const ThermalModel = SimulationModel{<:JutulDomain, <:ThermalSystem, <:Any, <:Any} +const ThermalCompositionalModel = SimulationModel{<:JutulDomain, <:ThermalSystem{<:Any, <:CompositionalSystem}, <:Any, <:Any} +const ThermalBlackOilModel = SimulationModel{<:JutulDomain, <:ThermalSystem{<:Any, <:StandardBlackOilModel}, <:Any, <:Any} +const ThermalImmiscibleModel = SimulationModel{<:JutulDomain, <:ThermalSystem{<:Any, <:ImmiscibleSystem}, <:Any, <:Any} + struct BulkVolume <: ScalarVariable end function Jutul.default_values(model, ::BulkVolume) return 1.0 diff --git a/src/thermal/variables.jl b/src/thermal/variables.jl index 5b3fd090..40bd1aed 100644 --- a/src/thermal/variables.jl +++ b/src/thermal/variables.jl @@ -1,4 +1,4 @@ -@jutul_secondary function update_fluid_internal_energy!(U, fe::FluidInternalEnergy, model::ThermalModel, Temperature, ComponentHeatCapacity, ix) +@jutul_secondary function update_fluid_internal_energy!(U, fe::FluidInternalEnergy, model::ThermalImmiscibleModel, Temperature, ComponentHeatCapacity, ix) @assert size(U) == size(ComponentHeatCapacity) "This fluid internal energy implementation assumes immiscible phases." for i in ix T = Temperature[i] From 0d272fd67964534eedcd812d7ae4fe130e70c9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 20:55:01 +0100 Subject: [PATCH 67/92] Add compositional specialization of internal energy --- src/thermal/variables.jl | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/thermal/variables.jl b/src/thermal/variables.jl index 40bd1aed..357458ce 100644 --- a/src/thermal/variables.jl +++ b/src/thermal/variables.jl @@ -8,6 +8,36 @@ end end +@jutul_secondary function update_fluid_internal_energy!(U, fe::FluidInternalEnergy, model::ThermalCompositionalModel, Temperature, ComponentHeatCapacity, LiquidMassFractions, VaporMassFractions, ix) + fsys = flow_system(model.system) + C = ComponentHeatCapacity + X = LiquidMassFractions + Y = VaporMassFractions + if has_other_phase(fsys) + a, l, v = phase_indices(fsys) + offset = 1 + else + l, v = phase_indices(fsys) + offset = 0 + end + for i in ix + T = Temperature[i] + if has_other_phase(fsys) + C_w = C[1, i] + U[a, i] = C_w*T + end + U_v = 0.0 + U_l = 0.0 + for c in axes(X, 1) + C_i = C[c+offset, i] + U_l += C_i*X[c, i] + U_v += C_i*Y[c, i] + end + U[l, i] = U_l*T + U[v, i] = U_v*T + end +end + @jutul_secondary function update_fluid_enthalpy!(H, fe::FluidEnthalpy, model::ThermalModel, FluidInternalEnergy, Pressure, PhaseMassDensities, ix) for i in ix p = Pressure[i] From e2adf4728c3fa88b281c73ed2db9c7c5e9d85793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 21:27:30 +0100 Subject: [PATCH 68/92] Update equations.jl --- src/thermal/equations.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/thermal/equations.jl b/src/thermal/equations.jl index 6cd09a72..d7698440 100644 --- a/src/thermal/equations.jl +++ b/src/thermal/equations.jl @@ -28,14 +28,18 @@ function thermal_heat_flux(face, state, model, grad, upw, flux_type) λ_r = state.RockThermalConductivities λ_f = state.FluidThermalConductivities S = state.Saturations + nph = number_of_phases(model.system) - convective_flux = 0 - λ_total = λ_r[face] + convective_flux = 0.0 flow_common = kgrad_common(face, state, model, grad) - for α in 1:number_of_phases(model.system) + for α in 1:nph F_α = darcy_phase_mass_flux(face, α, state, model, flux_type, grad, upw, flow_common) H_face_α = phase_upwind(upw, H_f, α, F_α) convective_flux += H_face_α*F_α + end + + λ_total = λ_r[face] + for α in 1:nph λ_total += λ_f[α, face]*phase_face_average(S, grad, α) end conductive_flux = -λ_total*gradient(T, grad) From e8d5242909f737ebe7509b7ecf897796b6dd877f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 21:34:32 +0100 Subject: [PATCH 69/92] Fix to thermal flux for compositional --- src/multicomponent/flux.jl | 2 +- src/thermal/variables.jl | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/multicomponent/flux.jl b/src/multicomponent/flux.jl index 4ce3dcd1..767eb880 100644 --- a/src/multicomponent/flux.jl +++ b/src/multicomponent/flux.jl @@ -43,7 +43,7 @@ end return q end -function face_average_density(model::CompositionalModel, state, tpfa, phase) +function face_average_density(model::Union{CompositionalModel, ThermalCompositionalModel}, state, tpfa, phase) ρ = state.PhaseMassDensities s = state.Saturations l = tpfa.left diff --git a/src/thermal/variables.jl b/src/thermal/variables.jl index 357458ce..ffe0c1cf 100644 --- a/src/thermal/variables.jl +++ b/src/thermal/variables.jl @@ -1,9 +1,10 @@ @jutul_secondary function update_fluid_internal_energy!(U, fe::FluidInternalEnergy, model::ThermalImmiscibleModel, Temperature, ComponentHeatCapacity, ix) - @assert size(U) == size(ComponentHeatCapacity) "This fluid internal energy implementation assumes immiscible phases." + C = ComponentHeatCapacity + @assert size(U) == size(C) "This fluid internal energy implementation assumes immiscible phases." for i in ix T = Temperature[i] for c in axes(U, 1) - U[c, i] = ComponentHeatCapacity[c, i]*T + U[c, i] = C[c, i]*T end end end From e32ed9ac921683b9c7da1f97ec631751cb165995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 22:05:18 +0100 Subject: [PATCH 70/92] Move function to fix load order --- src/JutulDarcy.jl | 2 +- src/flux.jl | 25 +++++++++++++++++++++++++ src/multicomponent/flux.jl | 24 ------------------------ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/JutulDarcy.jl b/src/JutulDarcy.jl index d18531ac..c4299b08 100644 --- a/src/JutulDarcy.jl +++ b/src/JutulDarcy.jl @@ -152,7 +152,6 @@ module JutulDarcy include("porousmedia_grids.jl") include("utils.jl") include("interpolation.jl") - include("flux.jl") # Definitions for multiphase flow include("multiphase.jl") include("variables/variables.jl") @@ -167,6 +166,7 @@ module JutulDarcy # Wells etc. include("facility/facility.jl") + include("flux.jl") include("porousmedia.jl") # MRST inputs and test cases that use MRST input # and .DATA file simulation diff --git a/src/flux.jl b/src/flux.jl index 0c4faa1f..67a2444b 100644 --- a/src/flux.jl +++ b/src/flux.jl @@ -63,6 +63,31 @@ function face_average_density(model, state, tpfa, phase) return 0.5*(ρ_i + ρ_c) end +function face_average_density(model::Union{CompositionalModel, ThermalCompositionalModel}, state, tpfa, phase) + ρ = state.PhaseMassDensities + s = state.Saturations + l = tpfa.left + r = tpfa.right + ϵ = MINIMUM_COMPOSITIONAL_SATURATION + @inbounds s_l = s[phase, l] + @inbounds s_r = s[phase, r] + @inbounds ρ_l = ρ[phase, l] + @inbounds ρ_r = ρ[phase, r] + + s_l_tiny = s_l <= ϵ + s_r_tiny = s_r <= ϵ + if s_l_tiny && s_r_tiny + ρ_avg = zero(s_l) + elseif s_l_tiny + ρ_avg = ρ_r + elseif s_r_tiny + ρ_avg = ρ_l + else + ρ_avg = (s_l*ρ_r + s_r*ρ_l)/(s_l + s_r) + end + return ρ_avg +end + @inline function gradient(X::AbstractVector, tpfa::TPFA) return @inbounds X[tpfa.right] - X[tpfa.left] end diff --git a/src/multicomponent/flux.jl b/src/multicomponent/flux.jl index 767eb880..438ec086 100644 --- a/src/multicomponent/flux.jl +++ b/src/multicomponent/flux.jl @@ -43,27 +43,3 @@ end return q end -function face_average_density(model::Union{CompositionalModel, ThermalCompositionalModel}, state, tpfa, phase) - ρ = state.PhaseMassDensities - s = state.Saturations - l = tpfa.left - r = tpfa.right - ϵ = MINIMUM_COMPOSITIONAL_SATURATION - @inbounds s_l = s[phase, l] - @inbounds s_r = s[phase, r] - @inbounds ρ_l = ρ[phase, l] - @inbounds ρ_r = ρ[phase, r] - - s_l_tiny = s_l <= ϵ - s_r_tiny = s_r <= ϵ - if s_l_tiny && s_r_tiny - ρ_avg = zero(s_l) - elseif s_l_tiny - ρ_avg = ρ_r - elseif s_r_tiny - ρ_avg = ρ_l - else - ρ_avg = (s_l*ρ_r + s_r*ρ_l)/(s_l + s_r) - end - return ρ_avg -end From f53f80bbf47b79d7a30117e6ca408618cae9ab97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 24 Feb 2024 22:09:11 +0100 Subject: [PATCH 71/92] Make InjectorControl fully parametric --- src/facility/types.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/facility/types.jl b/src/facility/types.jl index 0dd778bc..8e33a5ac 100644 --- a/src/facility/types.jl +++ b/src/facility/types.jl @@ -313,14 +313,15 @@ if not given. See also [`ProducerControl`](@ref), [`DisabledControl`](@ref). """ -struct InjectorControl{T, R} <: WellControlForce +struct InjectorControl{T, R, P, M, E} <: WellControlForce target::T - injection_mixture + injection_mixture::M mixture_density::R - phases + phases::P temperature::R + enthalpy::E factor::R - function InjectorControl(target::T, mix; density::R = 1.0, phases = ((1, 1.0),), temperature::R = 273.15, factor::R = 1.0) where {T<:WellTarget, R<:Real} + function InjectorControl(target::T, mix; density::R = 1.0, phases = ((1, 1.0),), temperature::R = 273.15, enthalpy = missing, factor::R = 1.0) where {T<:WellTarget, R<:Real} @assert isfinite(density) && density > 0.0 "Injector density must be finite and positive" @assert isfinite(temperature) && temperature > 0.0 "Injector temperature must be finite and positive" @@ -329,7 +330,7 @@ struct InjectorControl{T, R} <: WellControlForce end mix = vec(mix) @assert sum(mix) ≈ 1 - new{T, R}(target, mix, density, phases, temperature, factor) + new{T, R, typeof(phases), typeof(mix), typeof(enthalpy)}(target, mix, density, phases, temperature, enthalpy, factor) end end replace_target(f::InjectorControl, target) = InjectorControl(target, f.injection_mixture, density = f.mixture_density, phases = f.phases, factor = f.factor) From 1d57f6dc868ecdb31b69a833bfab59d7f841b90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sun, 25 Feb 2024 20:46:01 +0100 Subject: [PATCH 72/92] Add more fields to deck types --- src/deck_types.jl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/deck_types.jl b/src/deck_types.jl index 9681addd..1bfca069 100644 --- a/src/deck_types.jl +++ b/src/deck_types.jl @@ -8,13 +8,17 @@ abstract type AbstractTablePVT <: AbstractReservoirDeckTable end Secondary variable used to evaluate viscosities when a case is generated from a input file. Typically not instantiated in user scripts. """ -struct DeckPhaseViscosities{T, R} <: DeckPhaseVariables +struct DeckPhaseViscosities{T, M, R} <: DeckPhaseVariables pvt::T + thermal::M regions::R - function DeckPhaseViscosities(pvt; regions = nothing) + function DeckPhaseViscosities(pvt; regions = nothing, thermal = nothing) check_regions(regions) pvt_t = Tuple(pvt) - new{typeof(pvt_t), typeof(regions)}(pvt_t, regions) + if !isnothing(thermal) + thermal = Tuple(thermal) + end + new{typeof(pvt_t), typeof(thermal), typeof(regions)}(pvt_t, thermal, regions) end end @@ -40,13 +44,15 @@ DeckShrinkageFactors(pvt, regions = nothing) Secondary variable used to evaluate shrinkage factors when a case is generated from a input file. Typically not instantiated in user scripts. """ -struct DeckShrinkageFactors{T, R} <: DeckPhaseVariables +struct DeckShrinkageFactors{T, W, R} <: DeckPhaseVariables pvt::T + watdent::W regions::R - function DeckShrinkageFactors(pvt; regions = nothing) + function DeckShrinkageFactors(pvt; watdent = nothing, regions = nothing) check_regions(regions) pvt_t = Tuple(pvt) - new{typeof(pvt_t), typeof(regions)}(pvt_t, regions) + watdent_t = region_wrap(watdent, regions) + new{typeof(pvt_t), typeof(watdent_t), typeof(regions)}(pvt_t, watdent, regions) end end From 12777f85d28fdb6d98035b170744c5f31c5d3c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sun, 25 Feb 2024 20:59:32 +0100 Subject: [PATCH 73/92] Add WATDENT to setup (not eval) --- src/deck_types.jl | 20 +++++++++++++++++--- src/input_simulation/data_input.jl | 14 +++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/deck_types.jl b/src/deck_types.jl index 1bfca069..313f7d7e 100644 --- a/src/deck_types.jl +++ b/src/deck_types.jl @@ -28,13 +28,15 @@ end Secondary variable used to evaluate densities when a case is generated from a input file. Typically not instantiated in user scripts. """ -struct DeckPhaseMassDensities{T, R} <: DeckPhaseVariables +struct DeckPhaseMassDensities{T, W, R} <: DeckPhaseVariables pvt::T + watdent::W regions::R - function DeckPhaseMassDensities(pvt; regions = nothing) + function DeckPhaseMassDensities(pvt; regions = nothing, watdent = nothing) check_regions(regions) pvt_t = Tuple(pvt) - new{typeof(pvt_t), typeof(regions)}(pvt_t, regions) + watdent_t = region_wrap(watdent, regions) + new{typeof(pvt_t), typeof(watdent_t), typeof(regions)}(pvt_t, watdent_t, regions) end end @@ -382,6 +384,18 @@ function PVTW(pvtw::AbstractArray) PVTW{N, T}(ct) end +struct WATDENT{N, T} <: AbstractTablePVT + tab::NTuple{N, T} +end + +function WATDENT(watdent::AbstractArray) + c = map(rec -> (T = rec[1], c1 = rec[2], c2 = rec[3]), watdent) + ct = Tuple(c) + N = length(ct) + T = typeof(ct[1]) + return WATDENT{N, T}(ct) +end + struct PVCDO{N, T} <: AbstractTablePVT tab::NTuple{N, T} end diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index c0fb02d5..1a9d6723 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -103,9 +103,15 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans pvt_i = pvt end pvt_i = tuple(pvt_i...) - rho = DeckPhaseMassDensities(pvt_i, regions = pvt_reg_i) + + if is_thermal && haskey(datafile["PROPS"], "WATDENT") + watdent = WATDENT(datafile["PROPS"]["WATDENT"]) + else + watdent = nothing + end + rho = DeckPhaseMassDensities(pvt_i, regions = pvt_reg_i, watdent = watdent) if sys isa StandardBlackOilSystem - b_i = DeckShrinkageFactors(pvt_i, regions = pvt_reg_i) + b_i = DeckShrinkageFactors(pvt_i, regions = pvt_reg_i, watdent = watdent) set_secondary_variables!(submodel, ShrinkageFactors = wrap_reservoir_variable(sys, b_i, :flow) ) @@ -579,6 +585,7 @@ function parse_physics_types(datafile; pvt_region = missing) has_gas = has("GAS") has_disgas = has("DISGAS") has_vapoil = has("VAPOIL") + has_thermal = has("THERMAL") is_immiscible = !has_disgas && !has_vapoil is_compositional = haskey(runspec, "COMPS") @@ -631,7 +638,8 @@ function parse_physics_types(datafile; pvt_region = missing) end if has_wat - push!(pvt, deck_pvt_water(props, scaling = scaling)) + water_pvt = deck_pvt_water(props, scaling = scaling) + push!(pvt, water_pvt) push!(phases, AqueousPhase()) push!(rhoS, rhoWS) end From 6a566f822ea2185053a0c82d3a555e77791aea2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sun, 25 Feb 2024 21:16:09 +0100 Subject: [PATCH 74/92] Put in place some more thermal stuff --- src/deck_types.jl | 32 +++++++++++++++++++++++++++--- src/input_simulation/data_input.jl | 24 +++++++++++++++------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/deck_types.jl b/src/deck_types.jl index 313f7d7e..ed57ebc0 100644 --- a/src/deck_types.jl +++ b/src/deck_types.jl @@ -15,9 +15,7 @@ struct DeckPhaseViscosities{T, M, R} <: DeckPhaseVariables function DeckPhaseViscosities(pvt; regions = nothing, thermal = nothing) check_regions(regions) pvt_t = Tuple(pvt) - if !isnothing(thermal) - thermal = Tuple(thermal) - end + thermal::Union{Nothing, DeckThermalViscosityTable} new{typeof(pvt_t), typeof(thermal), typeof(regions)}(pvt_t, thermal, regions) end end @@ -384,6 +382,34 @@ function PVTW(pvtw::AbstractArray) PVTW{N, T}(ct) end +struct DeckThermalViscosityTable{T, V} + visc_tab::T + p_ref::V + rs_ref::V +end + +function DeckThermalViscosityTable(props::AbstractDict, pvt, water, oil, gas) + visc_tab = [] + function tab_to_interp(tab) + return map(x -> get_1d_interpolator(x[:, 1] .+ 273.15, x[:, 2]), tab) + end + vref = props["VISCREF"] + rs_ref = map(x -> x[2], vref) + p_ref = map(x -> x[1], vref) + if water + push!(visc_tab, tab_to_interp(props["WATVISCT"])) + end + if oil + push!(visc_tab, tab_to_interp(props["OILVISCT"])) + end + if gas + push!(visc_tab, tab_to_interp(props["GASVISCT"])) + end + visc_tab = Tuple(visc_tab) + return DeckThermalViscosityTable(visc_tab, p_ref, rs_ref) +end + + struct WATDENT{N, T} <: AbstractTablePVT tab::NTuple{N, T} end diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 1a9d6723..ac7344c3 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -67,6 +67,11 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans oil = haskey(rs, "OIL") water = haskey(rs, "WATER") gas = haskey(rs, "GAS") + if haskey(datafile, "PROPS") + props = datafile["PROPS"] + else + props = Dict{String, Any}() + end msg("Parsing reservoir domain.") domain = parse_reservoir(datafile) @@ -104,8 +109,8 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans end pvt_i = tuple(pvt_i...) - if is_thermal && haskey(datafile["PROPS"], "WATDENT") - watdent = WATDENT(datafile["PROPS"]["WATDENT"]) + if is_thermal && haskey(props, "WATDENT") + watdent = WATDENT(props["WATDENT"]) else watdent = nothing end @@ -116,25 +121,30 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans ShrinkageFactors = wrap_reservoir_variable(sys, b_i, :flow) ) end - mu = DeckPhaseViscosities(pvt_i, regions = pvt_reg_i) + if is_thermal && haskey(props, "VISCREF") + thermal_visc = DeckThermalViscosityTable(props, pvt_i, water, oil, gas) + else + thermal_visc = nothing + end + mu = DeckPhaseViscosities(pvt_i, regions = pvt_reg_i, thermal = thermal_visc) set_secondary_variables!(submodel, PhaseViscosities = wrap_reservoir_variable(sys, mu, :flow), PhaseMassDensities = wrap_reservoir_variable(sys, rho, :flow) ) end if is_thermal - set_thermal_deck_specialization!(submodel, datafile["PROPS"], domain[:pvtnum], oil, water, gas) + set_thermal_deck_specialization!(submodel, props, domain[:pvtnum], oil, water, gas) end if k == :Reservoir - set_deck_specialization!(submodel, datafile["PROPS"], domain[:satnum], oil, water, gas) + set_deck_specialization!(submodel, props, domain[:satnum], oil, water, gas) end end end msg("Setting up parameters.") parameters = setup_parameters(model) - if haskey(datafile["PROPS"], "SWL") + if haskey(props, "SWL") G = physical_representation(domain) - swl = vec(datafile["PROPS"]["SWL"]) + swl = vec(props["SWL"]) parameters[:Reservoir][:ConnateWater] .= swl[G.cell_map] end if use_ijk_trans From cd5f7d603275d33d6c26452b8c706089442e0a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 26 Feb 2024 13:52:49 +0100 Subject: [PATCH 75/92] Switch dp abs to use MPa as scaling --- src/multicomponent/multicomponent.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multicomponent/multicomponent.jl b/src/multicomponent/multicomponent.jl index 2f892bdf..f1f388eb 100644 --- a/src/multicomponent/multicomponent.jl +++ b/src/multicomponent/multicomponent.jl @@ -93,8 +93,8 @@ function convergence_criterion(model::SimulationModel{<:Any, S}, storage, eq::Co names = model.system.components R = ( CNV = (errors = e, names = names), - increment_dp_abs = (errors = (dp_abs, ), names = (raw"Δp", ), ), - increment_dp_rel = (errors = (dp_rel, ), names = (raw"Δp", ), ), + increment_dp_abs = (errors = (dp_abs/1e6, ), names = (raw"Δp (abs, MPa)", ), ), + increment_dp_rel = (errors = (dp_rel, ), names = (raw"Δp (rel)", ), ), increment_dz = (errors = (dz, ), names = (raw"Δz", ), ) ) return R From 359e8131f0f5f60049e2a563cfa3c37ceb6dcfa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 26 Feb 2024 13:53:48 +0100 Subject: [PATCH 76/92] Update utils.jl --- src/utils.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 4c4dbc7f..0d36baf6 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -493,6 +493,9 @@ function set_default_cnv_mb_inner!(tol, model; inc_tol_dz = Inf ) sys = model.system + if model isa Jutul.CompositeModel && hasproperty(model.system, :flow) + sys = flow_system(model.system) + end if sys isa ImmiscibleSystem || sys isa BlackOilSystem || sys isa CompositionalSystem is_well = physical_representation(model) isa WellDomain if is_well From 6475e97920122a99b848d76ba9189bdcbc496e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 26 Feb 2024 13:56:41 +0100 Subject: [PATCH 77/92] Update utils.jl --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 0d36baf6..27c4e62f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -493,7 +493,7 @@ function set_default_cnv_mb_inner!(tol, model; inc_tol_dz = Inf ) sys = model.system - if model isa Jutul.CompositeModel && hasproperty(model.system, :flow) + if model isa Jutul.CompositeModel && hasproperty(model.system.systems, :flow) sys = flow_system(model.system) end if sys isa ImmiscibleSystem || sys isa BlackOilSystem || sys isa CompositionalSystem From c6dd9752110a347b429329a2583c9af87b898f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 26 Feb 2024 14:46:33 +0100 Subject: [PATCH 78/92] Quick implementation of thermal viscosities from input file --- src/deck_support.jl | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/deck_support.jl b/src/deck_support.jl index 3db88991..a124489b 100644 --- a/src/deck_support.jl +++ b/src/deck_support.jl @@ -1,14 +1,33 @@ @jutul_secondary function update_deck_viscosity!(mu, μ::DeckPhaseViscosities, model, Pressure, ix) pvt, reg = μ.pvt, μ.regions - @inbounds for ph in axes(mu, 1) - pvt_ph = pvt[ph] - for i in ix - p = Pressure[i] + @inbounds for i in ix + p = Pressure[i] + for ph in axes(mu, 1) + pvt_ph = pvt[ph] @inbounds mu[ph, i] = viscosity(pvt_ph, reg, p, i) end end end +@jutul_secondary function update_deck_viscosity!(mu, μ::DeckPhaseViscosities{<:Any, Ttab, <:Any}, model, Pressure, Temperature, ix) where Ttab<:DeckThermalViscosityTable + pvt, reg = μ.pvt, μ.regions + for i in ix + r_i = region(μ, i) + p = Pressure[i] + T = Temperature[i] + for ph in axes(mu, 1) + pvt_ph = pvt[ph] + pvt_thermal = table_by_region(μ.thermal.visc_tab[ph], r_i) + p_ref = table_by_region(μ.thermal.p_ref[ph], r_i) + + mu_p = viscosity(pvt_ph, reg, p, i) + mu_ref = viscosity(pvt_ph, reg, p_ref, i) + mu_thermal = pvt_thermal(T) + mu[ph, i] = mu_thermal*(mu_p/mu_ref) + end + end +end + @jutul_secondary function update_deck_density!(rho, ρ::DeckPhaseMassDensities, model, Pressure, ix) rhos = reference_densities(model.system) pvt, reg = ρ.pvt, ρ.regions From c70e27923214a9f25ce50b3f4d41e09d02eb9177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 26 Feb 2024 15:35:12 +0100 Subject: [PATCH 79/92] Support for WATDENT --- src/deck_support.jl | 27 +++++++++++++++++++++++++++ src/deck_types.jl | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/deck_support.jl b/src/deck_support.jl index a124489b..8a2f651f 100644 --- a/src/deck_support.jl +++ b/src/deck_support.jl @@ -42,6 +42,33 @@ end end end +@jutul_secondary function update_deck_density!(rho, ρ::DeckPhaseMassDensities{<:Any, <:WATDENT, <:Any}, model, Pressure, Temperature, ix) + rhos = reference_densities(model.system) + pvt, reg = ρ.pvt, ρ.regions + phases = get_phases(model.system) + # Note immiscible assumption + for i in ix + r_i = region(ρ, i) + p = Pressure[i] + T = Temperature[i] + for ph in axes(rho, 1) + rhos_ph = rhos[ph] + pvt_ph = pvt[ph] + if phases[ph] == AqueousPhase() + T_ref, c1, c2 = ρ.watdent.tab[r_i] + pvtw = pvt_ph.tab[r_i] + p_ref = pvtw.p_ref + B_pref = 1.0/shrinkage(pvt_ph, reg, p_ref, i) + Δp = pvtw.b_c*(p - p_ref) + ΔT = T - T_ref + B_w = B_pref*(1.0 - Δp)*(1.0 + c1*ΔT + c2*ΔT^2) + rho[ph, i] = rhos_ph/B_w + else + rho[ph, i] = rhos_ph*shrinkage(pvt_ph, reg, p, i) + end + end + end +end @jutul_secondary function update_deck_shrinkage!(b, ρ::DeckShrinkageFactors, model, Pressure, ix) pvt, reg = ρ.pvt, ρ.regions diff --git a/src/deck_types.jl b/src/deck_types.jl index ed57ebc0..8adbd532 100644 --- a/src/deck_types.jl +++ b/src/deck_types.jl @@ -33,8 +33,8 @@ struct DeckPhaseMassDensities{T, W, R} <: DeckPhaseVariables function DeckPhaseMassDensities(pvt; regions = nothing, watdent = nothing) check_regions(regions) pvt_t = Tuple(pvt) - watdent_t = region_wrap(watdent, regions) - new{typeof(pvt_t), typeof(watdent_t), typeof(regions)}(pvt_t, watdent_t, regions) + watdent::Union{Nothing, WATDENT} + new{typeof(pvt_t), typeof(watdent), typeof(regions)}(pvt_t, watdent, regions) end end From bba0e59f14378cbb9676f4cc5d524037efb09864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 26 Feb 2024 20:18:50 +0100 Subject: [PATCH 80/92] Add enthalpy mixing draft --- src/thermal/thermal.jl | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 0e92127b..c9ba6bf0 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -152,6 +152,47 @@ end return result end +struct PressureTemperatureDependentEnthalpy{T, R, N} <: VectorVariables + tab::T + regions::R + function PressureTemperatureDependentEnthalpy(tab; regions = nothing) + tab = region_wrap(tab, regions) + ex = first(tab) + N = length(ex(1e8, 273.15 + 30.0)) + new{typeof(tab), typeof(regions), N}(tab, regions) + end +end + +function Jutul.values_per_entity(model, ::PressureTemperatureDependentEnthalpy{T, R, N}) where {T, R, N} + return N +end + +@jutul_secondary function update_temperature_dependent_enthalpy!(H_phases, var::PressureTemperatureDependentEnthalpy{T, R, N}, model::ThermalCompositionalModel, Pressure, Temperature, LiquidMassFractions, VaporMassFractions, PhaseMassDensities, ix) where {T, R, N} + fsys = flow_system(model.system) + @assert !has_other_phase(fsys) + @assert N == number_of_components(fsys) + l, v = phase_indices(fsys) + + X, Y = LiquidMassFractions, VaporMassFractions + rho = PhaseMassDensities + for c in ix + reg = region(var.regions, c) + interpolator = table_by_region(var.tab, reg) + component_H = interpolator(Pressure[c], Temperature[c]) + H_l = 0.0 + H_v = 0.0 + for i in 1:N + H_i = component_H[i] + H_l += X[i, c]*H_i + H_v += Y[i, c]*H_i + end + # p = Pressure[c] + H_phases[l, c] = H_l# + p/rho[l, c] + H_phases[v, c] = H_v# + p/rho[v, c] + end + return H_phases +end + """ FluidThermalConductivities() From dddb157ab642d32ace9d6803bd885bd1b6f82295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 26 Feb 2024 20:34:36 +0100 Subject: [PATCH 81/92] Handle WTEMP order like WEFAC etc --- src/input_simulation/data_input.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index ac7344c3..ec964933 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -798,7 +798,7 @@ function parse_control_steps(runspec, props, schedule, sys) push!(cstep, ctrl_ix) end - skip = ("WELLSTRE", "WINJGAS", "GINJGAS", "GRUPINJE", "WELLINJE", "WEFAC") + skip = ("WELLSTRE", "WINJGAS", "GINJGAS", "GRUPINJE", "WELLINJE", "WEFAC", "WTEMP") bad_kw = Dict{String, Bool}() for (ctrl_ix, step) in enumerate(steps) found_time = false @@ -808,6 +808,12 @@ function parse_control_steps(runspec, props, schedule, sys) well_factor[wk[1]] = wk[2] end end + if haskey(step, "WTEMP") + for wk in step["WTEMP"] + wnm, wt = wk + well_temp[wnm] = convert_to_si(wt, :Celsius) + end + end for (key, kword) in pairs(step) if key == "DATES" if ismissing(start_date) @@ -875,11 +881,6 @@ function parse_control_steps(runspec, props, schedule, sys) for wk in kword apply_welopen!(controls, compdat, wk, active_controls) end - elseif key == "WTEMP" - for wk in kword - wnm, wt = wk - well_temp[wnm] = convert_to_si(wt, :Celsius) - end elseif key in skip # Already handled else From c4fb95f19fa039a03db514313df5439c3713b644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 27 Feb 2024 10:31:51 +0100 Subject: [PATCH 82/92] Improve thermal viscosity logic --- src/deck_support.jl | 12 ++++++++---- src/deck_types.jl | 12 +++++++++--- src/input_simulation/data_input.jl | 8 ++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/deck_support.jl b/src/deck_support.jl index 8a2f651f..54f96033 100644 --- a/src/deck_support.jl +++ b/src/deck_support.jl @@ -19,11 +19,15 @@ end pvt_ph = pvt[ph] pvt_thermal = table_by_region(μ.thermal.visc_tab[ph], r_i) p_ref = table_by_region(μ.thermal.p_ref[ph], r_i) - - mu_p = viscosity(pvt_ph, reg, p, i) - mu_ref = viscosity(pvt_ph, reg, p_ref, i) mu_thermal = pvt_thermal(T) - mu[ph, i] = mu_thermal*(mu_p/mu_ref) + if isfinite(p_ref) + # We have pressure dependence in addition to temperature + # dependence. + mu_p = viscosity(pvt_ph, reg, p, i) + mu_ref = viscosity(pvt_ph, reg, p_ref, i) + mu_thermal *= mu_p/mu_ref + end + mu[ph, i] = mu_thermal end end end diff --git a/src/deck_types.jl b/src/deck_types.jl index 8adbd532..b7ca23c1 100644 --- a/src/deck_types.jl +++ b/src/deck_types.jl @@ -393,9 +393,6 @@ function DeckThermalViscosityTable(props::AbstractDict, pvt, water, oil, gas) function tab_to_interp(tab) return map(x -> get_1d_interpolator(x[:, 1] .+ 273.15, x[:, 2]), tab) end - vref = props["VISCREF"] - rs_ref = map(x -> x[2], vref) - p_ref = map(x -> x[1], vref) if water push!(visc_tab, tab_to_interp(props["WATVISCT"])) end @@ -406,6 +403,15 @@ function DeckThermalViscosityTable(props::AbstractDict, pvt, water, oil, gas) push!(visc_tab, tab_to_interp(props["GASVISCT"])) end visc_tab = Tuple(visc_tab) + if haskey(props, "VISCREF") + vref = props["VISCREF"] + rs_ref = map(x -> x[2], vref) + p_ref = map(x -> x[1], vref) + else + nreg = length(first(visc_tab)) + rs_ref = fill(NaN, nreg) + p_ref = fill(NaN, nreg) + end return DeckThermalViscosityTable(visc_tab, p_ref, rs_ref) end diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index ec964933..fb002fd7 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -121,8 +121,12 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans ShrinkageFactors = wrap_reservoir_variable(sys, b_i, :flow) ) end - if is_thermal && haskey(props, "VISCREF") - thermal_visc = DeckThermalViscosityTable(props, pvt_i, water, oil, gas) + if is_thermal + if haskey(props, "WATVISCT") || haskey(props, "OILVISCT") || haskey(props, "GASVISCT") + thermal_visc = DeckThermalViscosityTable(props, pvt_i, water, oil, gas) + else + thermal_visc = nothing + end else thermal_visc = nothing end From 4aebac9e9b4e038641e4c901d6fc366427492cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 28 Feb 2024 21:17:28 +0100 Subject: [PATCH 83/92] Make use of InjectorControl enthalpy --- src/facility/cross_terms.jl | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/facility/cross_terms.jl b/src/facility/cross_terms.jl index b43f2735..9143088f 100644 --- a/src/facility/cross_terms.jl +++ b/src/facility/cross_terms.jl @@ -322,13 +322,22 @@ function well_top_node_enthalpy(ctrl::InjectorControl, model, state_well, cell) p = state_well.Pressure[cell] # density = ctrl.mixture_density T = ctrl.temperature - nph = size(state_well.Saturations, 1) - H = 0 - for ph in 1:nph - C = state_well.ComponentHeatCapacity[ph, cell] - dens = state_well.PhaseMassDensities[ph, cell] - S = state_well.Saturations[ph, cell] - H += S*(C*T + p/dens) + H_w = ctrl.enthalpy + if ismissing(H_w) + nph = size(state_well.Saturations, 1) + H = 0.0 + for ph in 1:nph + C = state_well.ComponentHeatCapacity[ph, cell] + dens = state_well.PhaseMassDensities[ph, cell] + S = state_well.Saturations[ph, cell] + H += S*(C*T + p/dens) + end + elseif H_w isa Real + H = H_w + elseif H_w isa Function + H = H_w(p, T) + else + error("InjectorControl.enthalpy must be missing, a real or a function.") end return H end From 28731921f5aac14423fbc90e1dd4d8374416a99a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 28 Feb 2024 21:43:15 +0100 Subject: [PATCH 84/92] Go via internal energy for well enthalpy when not specified --- src/facility/cross_terms.jl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/facility/cross_terms.jl b/src/facility/cross_terms.jl index 9143088f..b9831e23 100644 --- a/src/facility/cross_terms.jl +++ b/src/facility/cross_terms.jl @@ -324,20 +324,23 @@ function well_top_node_enthalpy(ctrl::InjectorControl, model, state_well, cell) T = ctrl.temperature H_w = ctrl.enthalpy if ismissing(H_w) - nph = size(state_well.Saturations, 1) H = 0.0 - for ph in 1:nph - C = state_well.ComponentHeatCapacity[ph, cell] - dens = state_well.PhaseMassDensities[ph, cell] + for ph in axes(state_well.Saturations, 1) + # Define it via the volume weighted internal energy S = state_well.Saturations[ph, cell] - H += S*(C*T + p/dens) + dens = state_well.PhaseMassDensities[ph, cell] + E_ph = state_well.FluidInternalEnergy[ph, cell] + H += S*(E_ph + p/dens) + # Alternative definition: + # C = state_well.ComponentHeatCapacity[ph, cell] + # H += S*(C*T + p/dens) end elseif H_w isa Real H = H_w elseif H_w isa Function H = H_w(p, T) else - error("InjectorControl.enthalpy must be missing, a real or a function.") + error("InjectorControl.enthalpy must be missing, a real or a function (p, T).") end return H end From 448e8cd831b8b8e0db16c58b4fffe964577f5f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 28 Feb 2024 22:23:12 +0100 Subject: [PATCH 85/92] Add diffusion to compositional flux --- src/multicomponent/flux.jl | 46 ++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/multicomponent/flux.jl b/src/multicomponent/flux.jl index 438ec086..d2f97628 100644 --- a/src/multicomponent/flux.jl +++ b/src/multicomponent/flux.jl @@ -5,41 +5,73 @@ X = state.LiquidMassFractions Y = state.VaporMassFractions + S = state.Saturations + ρ = state.PhaseMassDensities + if haskey(state, :Diffusivities) + D = state.Diffusivities + else + D = nothing + end kdisc = flux_primitives(face, state, model, flux_type, kgrad, upw) - q = compositional_fluxes!(q, face, state, X, Y, model, flux_type, kgrad, kdisc, upw, aqua, ph_ix) + q = compositional_fluxes!(q, face, state, S, ρ, X, Y, D, model, flux_type, kgrad, kdisc, upw, aqua, ph_ix) return q end -@inline function compositional_fluxes!(q, face, state, X, Y, model, flux_type, kgrad, kdisc, upw, aqua::Val{false}, phase_ix) +@inline function compositional_fluxes!(q, face, state, S, ρ, X, Y, D, model, flux_type, kgrad, kdisc, upw, aqua::Val{false}, phase_ix) nc = size(X, 1) l, v = phase_ix q_l = darcy_phase_mass_flux(face, l, state, model, flux_type, kgrad, upw, kdisc) q_v = darcy_phase_mass_flux(face, v, state, model, flux_type, kgrad, upw, kdisc) - q = inner_compositional!(q, X, Y, q_l, q_v, upw, nc) + q = inner_compositional!(q, S, ρ, X, Y, D, q_l, q_v, face, kgrad, upw, nc, phase_ix) return q end -@inline function compositional_fluxes!(q, face, state, X, Y, model, flux_type, kgrad, kdisc, upw, aqua::Val{true}, phase_ix) +@inline function compositional_fluxes!(q, face, state, S, ρ, X, Y, D, model, flux_type, kgrad, kdisc, upw, aqua::Val{true}, phase_ix) nc = size(X, 1) a, l, v = phase_ix q_a = darcy_phase_mass_flux(face, a, state, model, flux_type, kgrad, upw, kdisc) q_l = darcy_phase_mass_flux(face, l, state, model, flux_type, kgrad, upw, kdisc) q_v = darcy_phase_mass_flux(face, v, state, model, flux_type, kgrad, upw, kdisc) - q = inner_compositional!(q, X, Y, q_l, q_v, upw, nc) + q = inner_compositional!(q, S, ρ, X, Y, D, q_l, q_v, face, kgrad, upw, nc, (l, v)) q = setindex(q, q_a, nc+1) return q end -@inline function inner_compositional!(q, X, Y, q_l, q_v, upw, nc) +@inline function inner_compositional!(q, S, ρ, X, Y, D, q_l, q_v, face, grad, upw, nc, lv) for i in 1:nc X_f = upwind(upw, cell -> @inbounds(X[i, cell]), q_l) Y_f = upwind(upw, cell -> @inbounds(Y[i, cell]), q_v) - q_i = q_l*X_f + q_v*Y_f q = setindex(q, q_i, i) end + q = add_diffusive_component_flux(q, S, ρ, X, Y, D, face, grad, lv) return q end +function add_diffusive_component_flux(q, S, ρ, X, Y, D::Nothing, face, grad, lv) + return q +end + +function add_diffusive_component_flux(q, S, ρ, X, Y, D, face, grad, lv) + l, v = lv + diff_mass_l = phase_diffused_mass(D, ρ, l, S, face, grad) + diff_mass_v = phase_diffused_mass(D, ρ, v, S, face, grad) + + @inbounds for i in eachindex(q) + q_i = q[i] + dX = gradient(X, l, grad) + dY = gradient(Y, l, grad) + + q_i += diff_mass_l*dX + diff_mass_v*dY + q = setindex(q, q_i, i) + end + return q +end + +function phase_diffused_mass(D, ρ, α, S, face, grad) + @inbounds D_α = D[α, face] + mass_α = cell -> @inbounds ρ[α, cell]*S[α, cell] + return -D_α*face_average(mass_α, grad) +end From 0c89a0f11d04c0f4203fdc26fa4f1460b28d4197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Fri, 1 Mar 2024 12:02:32 +0100 Subject: [PATCH 86/92] Make thermal variable change configurable --- src/utils.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 27c4e62f..6227577e 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -156,6 +156,8 @@ function setup_reservoir_model(reservoir::DataDomain, system; p_min = DEFAULT_MINIMUM_PRESSURE, p_max = Inf, dr_max = Inf, + dT_max_rel = nothing, + dT_max_abs = nothing, parameters = Dict{Symbol, Any}(), kwarg... ) @@ -181,7 +183,9 @@ function setup_reservoir_model(reservoir::DataDomain, system; p_max = p_max, dr_max = dr_max, ds_max = ds_max, - dz_max = dz_max + dz_max = dz_max, + dT_max_rel = dT_max_rel, + dT_max_abs = dT_max_abs ) for k in extra_outputs @@ -220,7 +224,9 @@ function setup_reservoir_model(reservoir::DataDomain, system; p_max = p_max, dr_max = dr_max, ds_max = ds_max, - dz_max = dz_max + dz_max = dz_max, + dT_max_rel = dT_max_rel, + dT_max_abs = dT_max_abs ) models[wname] = wmodel if split_wells @@ -248,10 +254,11 @@ function setup_reservoir_model(reservoir::DataDomain, system; return out end -function set_reservoir_variable_defaults!(model; p_min, p_max, dp_max_abs, dp_max_rel, ds_max, dz_max, dr_max) +function set_reservoir_variable_defaults!(model; p_min, p_max, dp_max_abs, dp_max_rel, ds_max, dz_max, dr_max, dT_max_rel = nothing, dT_max_abs = nothing) # Replace various variables - if they are available replace_variables!(model, OverallMoleFractions = OverallMoleFractions(dz_max = dz_max), throw = false) replace_variables!(model, Saturations = Saturations(ds_max = ds_max), throw = false) + replace_variables!(model, Temperature = Temperature(max_rel = dT_max_rel, max_abs = dT_max_abs), throw = false) replace_variables!(model, ImmiscibleSaturation = ImmiscibleSaturation(ds_max = ds_max), throw = false) replace_variables!(model, BlackOilUnknown = BlackOilUnknown(ds_max = ds_max, dr_max = dr_max), throw = false) From ccba1aab8d00ff4d92980eb781e2446d43c7dd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sun, 3 Mar 2024 09:00:32 +0100 Subject: [PATCH 87/92] Use saturation min for diffusion Avoid cross-phase diffusion when phase is absent --- src/multicomponent/flux.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/multicomponent/flux.jl b/src/multicomponent/flux.jl index d2f97628..9004931b 100644 --- a/src/multicomponent/flux.jl +++ b/src/multicomponent/flux.jl @@ -72,6 +72,8 @@ end function phase_diffused_mass(D, ρ, α, S, face, grad) @inbounds D_α = D[α, face] - mass_α = cell -> @inbounds ρ[α, cell]*S[α, cell] - return -D_α*face_average(mass_α, grad) + den_α = cell -> @inbounds ρ[α, cell] + # Take minimum - diffusion should not cross phase boundaries. + S = min(S[α, grad.left], S[α, grad.right]) + return -D_α*S*face_average(den_α, grad) end From b73ca17474c12b7b802404d5c752764ddd1dec07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 4 Mar 2024 09:27:46 +0100 Subject: [PATCH 88/92] Patch to well enthalpy --- src/facility/cross_terms.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/facility/cross_terms.jl b/src/facility/cross_terms.jl index b9831e23..2eede215 100644 --- a/src/facility/cross_terms.jl +++ b/src/facility/cross_terms.jl @@ -329,11 +329,8 @@ function well_top_node_enthalpy(ctrl::InjectorControl, model, state_well, cell) # Define it via the volume weighted internal energy S = state_well.Saturations[ph, cell] dens = state_well.PhaseMassDensities[ph, cell] - E_ph = state_well.FluidInternalEnergy[ph, cell] - H += S*(E_ph + p/dens) - # Alternative definition: - # C = state_well.ComponentHeatCapacity[ph, cell] - # H += S*(C*T + p/dens) + C = state_well.ComponentHeatCapacity[ph, cell] + H += S*(C*T + p/dens) end elseif H_w isa Real H = H_w From 548fd846b6e25f7dd348ec8ab35ef85b81f72017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 4 Mar 2024 15:08:13 +0100 Subject: [PATCH 89/92] Fix to function dispatch for K-values --- src/multicomponent/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multicomponent/utils.jl b/src/multicomponent/utils.jl index e2f27064..2b7376e6 100644 --- a/src/multicomponent/utils.jl +++ b/src/multicomponent/utils.jl @@ -52,7 +52,7 @@ function (k::KValueWrapper{<:Any, :pT})(cond::NamedTuple) return val end -function (k::KValueWrapper{<:Any, :T})(cond::NamedTuple) +function (k::KValueWrapper{<:Any, :pTz})(cond::NamedTuple) val = k.K(cond.p, cond.T, cond.z) return val end From 6bab3131887180e187e08112b17fe3b404c93fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 4 Mar 2024 16:25:17 +0100 Subject: [PATCH 90/92] Clean up old code --- src/formulations/sequential/interface.jl | 2 -- src/multicomponent/variables/density.jl | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/formulations/sequential/interface.jl b/src/formulations/sequential/interface.jl index 3858d1b1..d1988f87 100644 --- a/src/formulations/sequential/interface.jl +++ b/src/formulations/sequential/interface.jl @@ -67,14 +67,12 @@ function convert_to_sequential(model::MultiModel; pressure = true, kwarg...) else source_equation = :pressure end - @info "OK" target source cross_term = PressureReservoirFromWellFlowCT(cross_term) ctp = Jutul.CrossTermPair(target, source, target_equation, source_equation, cross_term) end end push!(ct, ctp) end - @info "?!" ct smodel = convert_to_sequential(model[:Reservoir]; pressure = pressure, kwarg...) models = Dict{Symbol, Any}() for (k, v) in pairs(model.models) diff --git a/src/multicomponent/variables/density.jl b/src/multicomponent/variables/density.jl index 64a960e5..1f197a02 100644 --- a/src/multicomponent/variables/density.jl +++ b/src/multicomponent/variables/density.jl @@ -61,6 +61,7 @@ end rho[v, i] = rho_co2 rho[l, i] = rho_brine end + @info "Density" value.(extrema(rho)) end function co2_brine_mixture_density(T, c1, c2, c3, c4, rho_h2o_pure, X_co2) From 9184a0a18b0c4bc027a82ad2b3626321710af40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 4 Mar 2024 16:44:43 +0100 Subject: [PATCH 91/92] Fix to test --- test/multimodel.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/multimodel.jl b/test/multimodel.jl index 9599d3c5..9a4393c3 100644 --- a/test/multimodel.jl +++ b/test/multimodel.jl @@ -9,7 +9,7 @@ function test_compositional_with_wells(; kwarg...) @testset "Reservoir" begin res = states[end][:Reservoir] p = res[:Pressure] - p_ref = [5.21689677531206e6, 5.179716466955712e6, 5.136721581520829e6] + p_ref = [5.217526602862003e6, 5.180267757745085e6, 5.137145631361415e6] @test isapprox(p, p_ref, rtol = 1e-4) z = res[:OverallMoleFractions] z_ref = [ @@ -60,7 +60,7 @@ function test_blackoil_with_wells(; kwarg...) @testset "Reservoir" begin res = states[end][:Reservoir] p = res[:Pressure] - p_ref = [5.304935198089932e6, 5.409387646073289e6, 5.471340338832063e6] + p_ref = [5.305212867881992e6, 5.409995957652735e6, 5.472027929725644e6] @test isapprox(p, p_ref, rtol = 1e-4) sw = res[:ImmiscibleSaturation] From 2c40d6ebb9df061fd6b2415e492f2af6b66b7c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 4 Mar 2024 17:45:50 +0100 Subject: [PATCH 92/92] Simplify bouyancy term when properties exist --- src/flux.jl | 33 +++++++++++++++++----------- src/multicomponent/multicomponent.jl | 21 ++++++++++++++++++ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/flux.jl b/src/flux.jl index 67a2444b..f6749c62 100644 --- a/src/flux.jl +++ b/src/flux.jl @@ -64,26 +64,33 @@ function face_average_density(model, state, tpfa, phase) end function face_average_density(model::Union{CompositionalModel, ThermalCompositionalModel}, state, tpfa, phase) + sys = flow_system(model.system) ρ = state.PhaseMassDensities - s = state.Saturations l = tpfa.left r = tpfa.right - ϵ = MINIMUM_COMPOSITIONAL_SATURATION - @inbounds s_l = s[phase, l] - @inbounds s_r = s[phase, r] @inbounds ρ_l = ρ[phase, l] @inbounds ρ_r = ρ[phase, r] - s_l_tiny = s_l <= ϵ - s_r_tiny = s_r <= ϵ - if s_l_tiny && s_r_tiny - ρ_avg = zero(s_l) - elseif s_l_tiny - ρ_avg = ρ_r - elseif s_r_tiny - ρ_avg = ρ_l + if properties_present_when_saturation_is_zero(sys) + # We can safely use the standard approximation + ρ_avg = 0.5*(ρ_r + ρ_l) else - ρ_avg = (s_l*ρ_r + s_r*ρ_l)/(s_l + s_r) + s = state.Saturations + ϵ = MINIMUM_COMPOSITIONAL_SATURATION + @inbounds s_l = s[phase, l] + @inbounds s_r = s[phase, r] + + s_l_tiny = s_l <= ϵ + s_r_tiny = s_r <= ϵ + if s_l_tiny && s_r_tiny + ρ_avg = zero(s_l) + elseif s_l_tiny + ρ_avg = ρ_r + elseif s_r_tiny + ρ_avg = ρ_l + else + ρ_avg = (s_l*ρ_r + s_r*ρ_l)/(s_l + s_r) + end end return ρ_avg end diff --git a/src/multicomponent/multicomponent.jl b/src/multicomponent/multicomponent.jl index f1f388eb..1a1dc366 100644 --- a/src/multicomponent/multicomponent.jl +++ b/src/multicomponent/multicomponent.jl @@ -1,6 +1,27 @@ using MultiComponentFlash const MINIMUM_COMPOSITIONAL_SATURATION = 1e-10 +function properties_present_when_saturation_is_zero(sys::Jutul.JutulSystem) + return true +end + +function properties_present_when_saturation_is_zero(sys) + return properties_present_when_saturation_is_zero(flow_system(sys)) +end + +function properties_present_when_saturation_is_zero(sys::MultiPhaseCompositionalSystemLV) + return properties_present_when_saturation_is_zero(sys.equation_of_state) +end + +function properties_present_when_saturation_is_zero(eos::KValuesEOS) + return true +end + +function properties_present_when_saturation_is_zero(eos::GenericCubicEOS) + return false +end + + @inline function is_pure_single_phase(s_immiscible) return s_immiscible > 1.0 - MINIMUM_COMPOSITIONAL_SATURATION end