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/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/deck_support.jl b/src/deck_support.jl index 3db88991..54f96033 100644 --- a/src/deck_support.jl +++ b/src/deck_support.jl @@ -1,14 +1,37 @@ @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_thermal = pvt_thermal(T) + 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 + @jutul_secondary function update_deck_density!(rho, ρ::DeckPhaseMassDensities, model, Pressure, ix) rhos = reference_densities(model.system) pvt, reg = ρ.pvt, ρ.regions @@ -23,6 +46,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 9681addd..b7ca23c1 100644 --- a/src/deck_types.jl +++ b/src/deck_types.jl @@ -8,13 +8,15 @@ 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) + thermal::Union{Nothing, DeckThermalViscosityTable} + new{typeof(pvt_t), typeof(thermal), typeof(regions)}(pvt_t, thermal, regions) end end @@ -24,13 +26,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::Union{Nothing, WATDENT} + new{typeof(pvt_t), typeof(watdent), typeof(regions)}(pvt_t, watdent, 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 @@ -376,6 +382,52 @@ 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 + 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) + 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 + + +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/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 diff --git a/src/facility/cross_terms.jl b/src/facility/cross_terms.jl index 408af65d..2eede215 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 @@ -305,27 +314,35 @@ 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] - 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) + H = 0.0 + for ph in axes(state_well.Saturations, 1) + # Define it via the volume weighted internal energy + S = state_well.Saturations[ph, cell] + dens = state_well.PhaseMassDensities[ph, cell] + 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 (p, T).") end 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/facility/types.jl b/src/facility/types.jl index 6b9e7bd9..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) @@ -504,8 +505,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 +531,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 +540,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 +559,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) diff --git a/src/flux.jl b/src/flux.jl index bf18e551..f6749c62 100644 --- a/src/flux.jl +++ b/src/flux.jl @@ -63,6 +63,38 @@ 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) + sys = flow_system(model.system) + ρ = state.PhaseMassDensities + l = tpfa.left + r = tpfa.right + @inbounds ρ_l = ρ[phase, l] + @inbounds ρ_r = ρ[phase, r] + + if properties_present_when_saturation_is_zero(sys) + # We can safely use the standard approximation + ρ_avg = 0.5*(ρ_r + ρ_l) + else + 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 + @inline function gradient(X::AbstractVector, tpfa::TPFA) return @inbounds X[tpfa.right] - X[tpfa.left] end @@ -80,7 +112,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 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/init/init.jl b/src/init/init.jl index 8dfb8029..8c08d6ac 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; @@ -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, @@ -85,8 +86,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] @@ -106,26 +115,35 @@ 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 + if !ismissing(T_z) + init[:Temperature] = T_z.(depths) end - kr = kr[:, cells] - init[:Saturations] = s - init[:Pressure] = init_reference_pressure(pressures, contacts, kr, pc, 2) return init end @@ -135,6 +153,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 +171,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 = unwrap_reservoir_variable(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}() @@ -189,20 +215,29 @@ 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 = convert_to_si(only(sol["RTEMP"]), :Celsius) + 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 = [] 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 = findall( - i ->satnum[i] == sreg && - pvtnum[i] == preg && - eqlnum[i] == ereg, - 1:ncells - ) - + cells_pvtnum = findall(isequal(preg), pvtnum) + cells = intersect_sorted(cells_pvtnum, cells_sat_and_pvt) ncells_reg = length(cells) if ncells_reg == 0 continue @@ -233,38 +268,49 @@ 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 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 + if is_single_phase 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) + 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, ) @@ -293,7 +339,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 @@ -316,6 +362,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 @@ -357,6 +404,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) @@ -396,7 +464,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 @@ -484,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] @@ -505,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) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index e2d6fdbe..fb002fd7 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -50,16 +50,37 @@ 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 + 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") + if haskey(datafile, "PROPS") + props = datafile["PROPS"] + else + props = Dict{String, Any}() + end + + msg("Parsing reservoir domain.") domain = parse_reservoir(datafile) pvt_reg = reservoir_regions(domain, :pvtnum) has_pvt = isnothing(pvt_reg) # Parse wells - wells, controls, limits, cstep, dt, well_forces = parse_schedule(domain, sys, datafile; simple_well = simple_well) - + msg("Parsing schedule.") + 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 = [] for w in wells @@ -76,7 +97,7 @@ function setup_case_from_parsed_data(datafile; simple_well = true, use_ijk_trans 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 if !is_compositional svar = submodel.secondary_variables @@ -87,35 +108,57 @@ 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(props, "WATDENT") + watdent = WATDENT(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, watdent = watdent) set_secondary_variables!(submodel, - ShrinkageFactors = DeckShrinkageFactors(pvt_i, regions = pvt_reg_i) + ShrinkageFactors = wrap_reservoir_variable(sys, b_i, :flow) ) end - mu = DeckPhaseViscosities(pvt_i, regions = pvt_reg_i) - set_secondary_variables!(submodel, PhaseViscosities = mu, PhaseMassDensities = rho) + 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 + 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, 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) + 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 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 @@ -315,7 +358,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}() @@ -510,6 +552,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) @@ -537,6 +599,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") @@ -589,7 +652,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 @@ -647,6 +711,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 @@ -697,8 +764,11 @@ 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 + active_controls = Dict{String, Any}() limits = Dict{String, Any}() streams = Dict{String, Any}() well_injection = Dict{String, Any}() @@ -706,10 +776,12 @@ 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 well_factor[k] = 1.0 + well_temp[k] = 273.15 + 20.0 end all_compdat = [] all_controls = [] @@ -722,12 +794,15 @@ 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 - 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 @@ -737,6 +812,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) @@ -795,7 +876,14 @@ 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 + end + elseif key == "WELOPEN" + for wk in kword + apply_welopen!(controls, compdat, wk, active_controls) end elseif key in skip # Already handled @@ -803,11 +891,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) @@ -906,7 +995,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) @@ -1036,7 +1125,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") @@ -1044,7 +1133,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 @@ -1242,3 +1331,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 = collect(keys(cdat)) + nperf = length(ijk) + first_num = max(first_num, 1) + if last_num < 1 + last_num = nperf + end + for i in 1:nperf + 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 pairs(current_cdat) + c[k] = v + end + c[:open] = is_open + cdat[ijk[i]] = (; c...) + end + end + end + end +end diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index 339f7f23..8d686178 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -365,8 +365,16 @@ 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") - 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 @@ -485,7 +493,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) @@ -694,23 +707,75 @@ 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_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, ComponentHeatCapacity = 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, 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 @@ -721,7 +786,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"]) @@ -733,11 +798,28 @@ 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 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/multicomponent/flux.jl b/src/multicomponent/flux.jl index 4ce3dcd1..9004931b 100644 --- a/src/multicomponent/flux.jl +++ b/src/multicomponent/flux.jl @@ -5,65 +5,75 @@ 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 face_average_density(model::CompositionalModel, 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] +function add_diffusive_component_flux(q, S, ρ, X, Y, D::Nothing, face, grad, lv) + return q +end - 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) +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 ρ_avg + return q +end + +function phase_diffused_mass(D, ρ, α, S, face, grad) + @inbounds D_α = D[α, face] + 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 diff --git a/src/multicomponent/multicomponent.jl b/src/multicomponent/multicomponent.jl index a69c0cc1..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 @@ -93,8 +114,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 @@ -118,7 +139,15 @@ function pressure_increments(model, state, update_report) 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 + elseif haskey(mf_report, :max) + v = mf_report.max + else + v = 1.0 + end + return v end function immiscible_increment(model, state, ::Missing) diff --git a/src/multicomponent/utils.jl b/src/multicomponent/utils.jl index c37e1083..2b7376e6 100644 --- a/src/multicomponent/utils.jl +++ b/src/multicomponent/utils.jl @@ -11,8 +11,48 @@ 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)) 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, :pTz})(cond::NamedTuple) + val = k.K(cond.p, cond.T, cond.z) + return val +end diff --git a/src/multicomponent/variables/density.jl b/src/multicomponent/variables/density.jl index a3b89197..1f197a02 100644 --- a/src/multicomponent/variables/density.jl +++ b/src/multicomponent/variables/density.jl @@ -32,3 +32,45 @@ 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.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} + 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"H₂O" "First component was $(cnames[1]), expected H₂O" + @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 + p = Pressure[i] + T = Temperature[i] + 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 + end + @info "Density" value.(extrema(rho)) +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 = 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 + 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/flash.jl b/src/multicomponent/variables/flash.jl index 0cc929b6..7699f827 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) @@ -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] @@ -254,17 +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 - - @. K = value(K_ad) - V = flash_2ph!(nothing, K, eos, c) + 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 pure_liquid = V <= 0.0 pure_vapor = V >= 1.0 @@ -274,15 +287,26 @@ 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 - V = convert(Num_t, V) + @inbounds for i in 1:ncomp + Z_i = Z[i] + x[i] = Z_i + y[i] = Z_i + end + 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) - @. x = liquid_mole_fraction(Z, K, V) - @. y = vapor_mole_fraction(x, K) + 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_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 @@ -296,19 +320,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 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] 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) diff --git a/src/multicomponent/variables/saturations.jl b/src/multicomponent/variables/saturations.jl index d84d35eb..40d2edcf 100644 --- a/src/multicomponent/variables/saturations.jl +++ b/src/multicomponent/variables/saturations.jl @@ -32,3 +32,42 @@ 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 + molar_masses = 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(molar_masses) + 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] + 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 + else + S_v = 0.0 + end + Sat[v, i] = S_v + Sat[l, i] = 1.0 - S_v + end +end 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 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 diff --git a/src/multiphase.jl b/src/multiphase.jl index effd337f..c55008c8 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 @@ -114,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 @@ -293,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/regions/regions.jl b/src/regions/regions.jl index 75120a07..4a834955 100644 --- a/src/regions/regions.jl +++ b/src/regions/regions.jl @@ -21,7 +21,15 @@ 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 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/thermal/equations.jl b/src/thermal/equations.jl index 76f287c3..d7698440 100644 --- a/src/thermal/equations.jl +++ b/src/thermal/equations.jl @@ -28,16 +28,28 @@ 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_α - λ_total += λ_f[α, face]*phase_upwind(upw, S, α, F_α) + end + + λ_total = λ_r[face] + for α in 1:nph + λ_total += λ_f[α, face]*phase_face_average(S, grad, α) end 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 diff --git a/src/thermal/thermal.jl b/src/thermal/thermal.jl index 64fbe44a..c9ba6bf0 100644 --- a/src/thermal/thermal.jl +++ b/src/thermal/thermal.jl @@ -1,24 +1,32 @@ """ - ThermalSystem(num_phases = 1, 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 - function ThermalSystem(; nphases = 1, formulation = :Temperature) +struct ThermalSystem{T, F} <: JutulSystem + flow_system::F + function ThermalSystem(sys::T; + formulation = :Temperature + ) where T<:Union{MultiPhaseSystem, Missing} @assert formulation == :Temperature - new{formulation}(nphases) + new{formulation, T}(sys) end 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} +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 @@ -41,6 +49,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 @@ -57,11 +76,128 @@ end struct RockInternalEnergy <: ScalarVariable end struct TotalThermalEnergy <: ScalarVariable end -struct FluidHeatCapacity <: PhaseVariables end -Jutul.default_value(model, ::FluidHeatCapacity) = 5000.0 +struct ComponentHeatCapacity <: ComponentVariables end +Jutul.default_value(model, ::ComponentHeatCapacity) = 4184.0 + +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[:component_heat_capacity]) + if T isa Vector + T = repeat(T', ncomp, 1) + else + @assert size(T, 1) == ncomp + end + else + T = fill(default_value(model, param), ncomp, number_of_cells(data_domain)) + end + return T +end + 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 + +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 + +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() + +Variable defining the fluid component conductivity. +""" struct FluidThermalConductivities <: VectorVariables end Jutul.variable_scale(::FluidThermalConductivities) = 1e-10 Jutul.minimum_value(::FluidThermalConductivities) = 0.0 @@ -82,7 +218,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 @@ -112,7 +248,8 @@ function Jutul.default_parameter_values(data_domain, model, param::RockThermalCo return T end -number_of_phases(t::ThermalSystem) = t.nph +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() @@ -125,15 +262,17 @@ 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) 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} diff --git a/src/thermal/variables.jl b/src/thermal/variables.jl index 5929f27e..ffe0c1cf 100644 --- a/src/thermal/variables.jl +++ b/src/thermal/variables.jl @@ -1,12 +1,44 @@ -@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::ThermalImmiscibleModel, Temperature, ComponentHeatCapacity, ix) + C = ComponentHeatCapacity + @assert size(U) == size(C) "This fluid internal energy implementation assumes immiscible phases." for i in ix T = Temperature[i] - for ph in axes(U, 1) - U[ph, i] = FluidHeatCapacity[ph, i]*T + for c in axes(U, 1) + U[c, i] = C[c, i]*T end 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] diff --git a/src/types.jl b/src/types.jl index cda2ac6d..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 @@ -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/utils.jl b/src/utils.jl index 0003cad3..6227577e 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -29,20 +29,28 @@ 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_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 | - +| 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 +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 + component_heat_capacity = 4184.0, # ~water rock_density = 2000.0, diffusion = missing, kwarg... @@ -148,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... ) @@ -173,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 @@ -212,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 @@ -240,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) @@ -445,18 +460,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...) +function simulate_reservoir(case::JutulCase; config = missing, 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...) @@ -478,6 +500,9 @@ function set_default_cnv_mb_inner!(tol, model; inc_tol_dz = Inf ) sys = model.system + 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 is_well = physical_representation(model) isa WellDomain if is_well @@ -704,7 +729,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 +737,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 @@ -741,7 +766,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}() @@ -762,6 +787,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 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 diff --git a/src/variables/relperm.jl b/src/variables/relperm.jl index 0d2478c8..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) @@ -430,12 +431,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) diff --git a/src/variables/variables.jl b/src/variables/variables.jl index c2c5441d..6ad4b973 100644 --- a/src/variables/variables.jl +++ b/src/variables/variables.jl @@ -6,11 +6,14 @@ 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::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) @@ -49,11 +52,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) 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] diff --git a/test/thermal.jl b/test/thermal.jl index 8b77cef3..757191da 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) @@ -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 @@ -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)