From e10c9cc6110197971636a5e8684cf2aba5625f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 11 Sep 2024 21:31:39 +0200 Subject: [PATCH 01/24] Add missing subvariable calls --- src/deck_types.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/deck_types.jl b/src/deck_types.jl index 5b611c25..128a8844 100644 --- a/src/deck_types.jl +++ b/src/deck_types.jl @@ -620,6 +620,15 @@ struct TableCompressiblePoreVolume{V, R} <: ScalarVariable end end +function Jutul.subvariable(p::TableCompressiblePoreVolume, map::FiniteVolumeGlobalMap) + c = map.cells + regions = Jutul.partition_variable_slice(p.regions, c) + return TableCompressiblePoreVolume( + p.tab, + regions = regions + ) +end + struct ScalarPressureTable{V, R} <: ScalarVariable tab::V regions::R @@ -630,6 +639,15 @@ struct ScalarPressureTable{V, R} <: ScalarVariable end end +function Jutul.subvariable(p::ScalarPressureTable, map::FiniteVolumeGlobalMap) + c = map.cells + regions = Jutul.partition_variable_slice(p.regions, c) + return ScalarPressureTable( + p.tab, + regions = regions + ) +end + @jutul_secondary function update_variable!(pv, Φ::ScalarPressureTable, model, Pressure, ix) @inbounds for i in ix reg = region(Φ.regions, i) From 32db3f2c7ec86cf3ca5a3456e73c2413175b5175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 11 Sep 2024 21:35:00 +0200 Subject: [PATCH 02/24] Improve generate_jutuldarcy_examples behavior and docs --- docs/src/index.md | 17 ++++++++++++++++- src/types.jl | 6 ++++-- src/utils.jl | 11 ++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index c43127fb..99424163 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -11,7 +11,22 @@ DocTestSetup = quote end ``` -Documentation for [JutulDarcy.jl](https://github.com/sintefmath/JutulDarcy.jl). The documentation is currently limited to docstrings and a series of examples. The examples are sorted by complexity. We suggest you start with [Gravity segregation example](Gravity segregation example). +Documentation for [JutulDarcy.jl](https://github.com/sintefmath/JutulDarcy.jl). The documentation consists of a mixture of technical documentation, docstrings and examples. The examples are sorted by complexity. We suggest you start with [Gravity segregation example](Gravity segregation example). + +To generate a folder that contains the examples locally, you can run the following code to create a folder `jutuldarcy_examples` in your current working directory: + +```julia +using JutulDarcy +generate_jutuldarcy_examples() +``` + +Alternatively, a folder can be specified if you want the examples to be placed outside your present working directory: + + +```julia +using JutulDarcy +generate_jutuldarcy_examples("/home/username/") +``` JutulDarcy builds upon the general features found in [Jutul.jl](https://github.com/sintefmath/Jutul.jl). You may also find it useful to look at the [Jutul.jl documentation](https://sintefmath.github.io/Jutul.jl/dev/). diff --git a/src/types.jl b/src/types.jl index fad7235c..e5e25ca7 100644 --- a/src/types.jl +++ b/src/types.jl @@ -544,10 +544,12 @@ end Create a specific reservoir simulation results that contains well curves, reservoir states, and so on. This is the return type from `simulate_reservoir`. -A `ReservoirSimResult` can be unpacked into well solutions and reservoir states: +A `ReservoirSimResult` can be unpacked into well solutions, reservoir states and +reporting times: + ```julia res_result::ReservoirSimResult -ws, states = res_result +ws, states, t = res_result ``` # Fields diff --git a/src/utils.jl b/src/utils.jl index d7baf4fa..eb069b42 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1905,13 +1905,16 @@ export generate_jutuldarcy_examples pth = pwd(), name = "jutuldarcy_examples"; makie = nothing, + project = true, force = false ) Make a copy of all JutulDarcy examples in `pth` in a subfolder `jutuldarcy_examples`. An error is thrown if the folder already exists, unless the `force=true`, in which case the folder will be overwritten and the existing -contents will be permanently lost. +contents will be permanently lost. If `project=true`, a `Project.toml` file will +be generated with the same dependencies as that of the doc build system that +contains everything needed to run the examples. The `makie` argument allows replacing the calls to GLMakie in the examples with another backend specified by a `String`. There are no checks performed if the @@ -1921,6 +1924,7 @@ function generate_jutuldarcy_examples( pth = pwd(), name = "jutuldarcy_examples"; makie = nothing, + project = true, force = false ) if !ispath(pth) @@ -1941,6 +1945,7 @@ function generate_jutuldarcy_examples( end proj_location = joinpath(jdir, "..", "docs", "Project.toml") cp(ex_dir, proj_location, force = true) + chmod(ex_dir, 0o777, recursive = true) return dest end @@ -1949,6 +1954,10 @@ function replace_makie_calls!(dest, makie) makie_str = "$makie" for (root, dirs, files) in walkdir(dest) for ex in files + if !endswith(lowercase(ex), ".jl") + # Don't mess with Project.toml + continue + end ex_pth = joinpath(root, ex) ex_lines = readlines(ex_pth, keep=true) f = open(ex_pth, "w") From ae506ca43cbd18514c65c5052b0fa23adcf74135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 12 Sep 2024 08:07:02 +0200 Subject: [PATCH 03/24] Update well docs --- src/facility/wells/wells.jl | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/facility/wells/wells.jl b/src/facility/wells/wells.jl index ca2ff90a..61c4f198 100644 --- a/src/facility/wells/wells.jl +++ b/src/facility/wells/wells.jl @@ -63,10 +63,21 @@ function common_well_setup(nr; dz = nothing, WI = nothing, gravity = gravity_con end """ - setup_well(D::DataDomain, reservoir_cells; skin = 0.0, Kh = nothing, radius = 0.1, dir = :z) + setup_well(D::DataDomain, reservoir_cells; skin = 0.0, Kh = nothing, radius = 0.1, dir = :z, name = :Well) + w = setup_well(D, 1, name = :MyWell) # Cell 1 in the grid + w = setup_well(D, (2, 5, 1), name = :MyWell) # Position (2, 5, 1) in logically structured mesh + w2 = setup_well(D, [1, 2, 3], name = :MyOtherWell) + Set up a well in `reservoir_cells` with given skin factor and radius. The order of cells matter as it is treated as a trajectory. + +The `name` keyword argument can be left defaulted if your model will only have a +single well (named `:Well`). It is highly recommended to provide this whenever a +well is set up. + +`reservoir_cells` can be one of the following: A Vector of cells, a single cell, +a Vector of `(I, J, K)` Tuples or a single Tuple of the same type. """ function setup_well(D::DataDomain, reservoir_cells; cell_centers = D[:cell_centroids], kwarg...) K = D[:permeability] @@ -205,10 +216,12 @@ function Jutul.plot_primitives(mesh::MultiSegmentWell, plot_type; kwarg...) end """ - setup_vertical_well(D::DataDomain, i, j; ) + setup_vertical_well(D::DataDomain, i, j; name = :MyWell, ) -Set up a vertical well with a `DataDomain` input that represents the porous -medium / reservoir where the wells it to be placed. +Set up a vertical well with a [`DataDomain`](@ref) input that represents the porous +medium / reservoir where the wells it to be placed. See [`SimpleWell`](@ref), +[`MultiSegmentWell`](@ref) and [`setup_well`](@ref) for more details about possible keyword +arguments. """ function setup_vertical_well(D::DataDomain, i, j; cell_centers = D[:cell_centroids], kwarg...) K = D[:permeability] From 511ddf2170b347020eccd62ba8f2bc502ac01210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 12 Sep 2024 20:09:21 +0200 Subject: [PATCH 04/24] Doc/example improvements --- examples/co2_brine_2d_vertical.jl | 17 +++++++---------- examples/co2_sloped.jl | 3 ++- src/types.jl | 7 ++++++- src/utils.jl | 12 ++++++++++++ 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/examples/co2_brine_2d_vertical.jl b/examples/co2_brine_2d_vertical.jl index 8da731e4..b9ef6441 100644 --- a/examples/co2_brine_2d_vertical.jl +++ b/examples/co2_brine_2d_vertical.jl @@ -19,7 +19,6 @@ bic = zeros(2, 2) mixture = MultiComponentMixture([h2o, co2], A_ij = bic, names = ["H2O", "CO2"]) eos = GenericCubicEOS(mixture, PengRobinson()) -#- # ## Set up domain and wells using Jutul, JutulDarcy, GLMakie nx = 50 @@ -35,7 +34,6 @@ res = reservoir_domain(g, porosity = 0.3, permeability = K) prod = setup_well(g, K, [(nx, ny, 1)], name = :Producer) # Set up an injector in the opposite corner, perforated in bottom layer inj = setup_well(g, K, [(1, 1, nz)], name = :Injector) -#- # ## Define system and realize on grid rhoLS = 844.23*kg/meter^3 rhoVS = 126.97*kg/meter^3 @@ -46,15 +44,15 @@ model, parameters = setup_reservoir_model(res, sys, wells = [inj, prod]); push!(model[:Reservoir].output_variables, :Saturations) kr = BrooksCoreyRelativePermeabilities(sys, 2.0, 0.0, 1.0) model = replace_variables!(model, RelativePermeabilities = kr) -T0 = repeat([303.15*Kelvin], 1, nc) +T0 = fill(303.15*Kelvin, nc) parameters[:Reservoir][:Temperature] = T0 state0 = setup_reservoir_state(model, Pressure = 50*bar, OverallMoleFractions = [1.0, 0.0]); # ## Define schedule # 5 year (5*365.24 days) simulation period -dt0 = repeat([1]*day, 26) -dt1 = repeat([10.0]*day, 180) -dt = append!(dt0, dt1) +dt0 = fill(1*day, 26) +dt1 = fill(10.0*day, 180) +dt = cat(dt0, dt1, dims = 1) rate_target = TotalRateTarget(9.5066e-06*meter^3/sec) I_ctrl = InjectorControl(rate_target, [0, 1], density = rhoVS) bhp_target = BottomHolePressureTarget(50*bar) @@ -78,18 +76,17 @@ function plot_vertical(x, t) Colorbar(fig[1, 2], plot) fig end -#- + # ### Plot final CO2 mole fraction plot_vertical(z, "CO2") -#- + # ### Plot final vapor saturation sg = states[end][:Saturations][2, :] plot_vertical(sg, "Vapor saturation") -#- + # ### Plot final pressure p = states[end][:Pressure] plot_vertical(p./bar, "Pressure [bar]") -#- # ### Plot in interactive viewer plot_reservoir(model, states, step = length(dt), key = :Saturations) diff --git a/examples/co2_sloped.jl b/examples/co2_sloped.jl index 221116e7..3142c9c4 100644 --- a/examples/co2_sloped.jl +++ b/examples/co2_sloped.jl @@ -23,7 +23,8 @@ Darcy, bar, kg, meter, day, yr = si_units(:darcy, :bar, :kilogram, :meter, :day, # surface has a large impact on where the CO2 migrates. cart_dims = (nx, 1, nz) physical_dims = (1000.0, 1.0, 50.0) -mesh = UnstructuredMesh(CartesianMesh(cart_dims, physical_dims)) +cart_mesh = CartesianMesh(cart_dims, physical_dims) +mesh = UnstructuredMesh(cart_mesh) points = mesh.node_points for (i, pt) in enumerate(points) diff --git a/src/types.jl b/src/types.jl index e5e25ca7..cbedcd85 100644 --- a/src/types.jl +++ b/src/types.jl @@ -34,9 +34,14 @@ const LVCompositionalModel2Phase = SimulationModel{D, S, F, C} where {D, S<:LVCo const LVCompositionalModel3Phase = SimulationModel{D, S, F, C} where {D, S<:LVCompositional3PhaseSystem, F, C} """ + MultiPhaseCompositionalSystemLV(equation_of_state) MultiPhaseCompositionalSystemLV(equation_of_state, phases = (LiquidPhase(), VaporPhase()); reference_densities = ones(length(phases)), other_name = "Water") -Set up a compositional system for a given `equation_of_state` from `MultiComponentFlash`. +Set up a compositional system for a given `equation_of_state` from +`MultiComponentFlash` with two or three phases. If three phases are provided, +the phase that is not a liquid or a vapor phase will be treated as immiscible in +subsequent simulations and given the name from `other_name` when listed as a +component. """ function MultiPhaseCompositionalSystemLV(equation_of_state, phases = (LiquidPhase(), VaporPhase()); reference_densities = ones(length(phases)), other_name = "Water") c = copy(MultiComponentFlash.component_names(equation_of_state)) diff --git a/src/utils.jl b/src/utils.jl index eb069b42..9867a1e4 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1720,6 +1720,18 @@ end export co2_inventory +""" + inventory = co2_inventory(model, ws, states, t; cells = missing, co2_name = "CO2") + inventory = co2_inventory(model, result::ReservoirSimResult; cells = missing) + +Compute CO2 inventory for each step for a given `model`, well results `ws` and +reporting times t. If provided, the keyword argument `cells` will compute +inventory inside the region defined by the cells, and let any additional CO2 be +categorized as "outside region". + +The inventory will be a Vector of Dicts where each entry contains a breakdown of +the status of the CO2 at that time, including residual and dissolution trapping. +""" function co2_inventory(model, ws, states, t; cells = missing, co2_name = "CO2") dt = diff(vcat(0, t)) @assert all(dt .> 0.0) "Fourth argument t should be time from simulation start. Maybe you passed timesteps?" From 6876b13fcf3f52b792ed4175ce83af50737b22b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 12 Sep 2024 20:17:03 +0200 Subject: [PATCH 05/24] Add doctest to kr --- src/types.jl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index cbedcd85..6822e6c5 100644 --- a/src/types.jl +++ b/src/types.jl @@ -264,7 +264,21 @@ 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. +value used to avoid extrapolation can be specified. The return type holds both +the table, the phase context, the autodetected critical and maximum relative +permeability values and can be passed saturation values to evaluate the +underlying function: + +```jldoctest +s = range(0, 1, 50) +k = s.^2 +kr = PhaseRelativePermeability(s, k) +round(kr(0.5), digits = 2) + +# output + +0.25 +``` """ function PhaseRelativePermeability(s, k; label = :w, connate = s[1], epsilon = 1e-16) s = collect(s) From 56a2e01413190106fa3ee85311b6917567b3e711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 12 Sep 2024 20:19:54 +0200 Subject: [PATCH 06/24] Add docs for Brooks-Corey --- src/variables/relperm/simple.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/variables/relperm/simple.jl b/src/variables/relperm/simple.jl index 6d32ca3e..7e68a376 100644 --- a/src/variables/relperm/simple.jl +++ b/src/variables/relperm/simple.jl @@ -173,7 +173,15 @@ end return kr end -function brooks_corey_relperm(s; n = 2, residual = 0, kr_max = 1.0, residual_total = residual) +""" + brooks_corey_relperm(s; n = 2.0, residual = 0.0, kr_max = 1.0, residual_total = residual) + +Evaluate Brooks-Corey relative permeability function at saturation `s` for +exponent `n` and a given residual and maximum relative permeability value. If +considering a two-phase system, the total residual value over both phases should +also be passed if the other phase has a non-zero residual value. +""" +function brooks_corey_relperm(s; n = 2.0, residual = 0.0, kr_max = 1.0, residual_total = residual) @assert s <= 1.0 @assert s >= 0.0 return brooks_corey_relperm(s, n, residual, kr_max, residual_total) From 737195b89e5676b470d7fae79658830dacbb7622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Mon, 16 Sep 2024 20:54:27 +0200 Subject: [PATCH 07/24] Begin adding some code for numerical aquifer --- src/input_simulation/data_input.jl | 38 ++++++++++++++++++++++++++++++ src/utils.jl | 19 ++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 0e98d7ee..54a5e2d5 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -740,6 +740,12 @@ function parse_reservoir(data_file; zcorn_depths = true) grid = data_file["GRID"] cartdims = grid["cartDims"] G = mesh_from_grid_section(grid) + + # Handle numerical aquifers + aqunum = get(grid, "AQUNUM", missing) + aqucon = get(grid, "AQUCON", missing) + # TODO: Export this properly + aquifers = GeoEnergyIO.CornerPointGrid.mesh_add_numerical_aquifers!(G, aqunum, aqucon) active_ix = G.cell_map nc = number_of_cells(G) nf = number_of_faces(G) @@ -761,6 +767,12 @@ function parse_reservoir(data_file; zcorn_depths = true) multpv[i] = v end end + if !isnothing(aquifers) + # Avoid MULTPV for aquifer cells + for (aq_id, aqprm) in pairs(aquifers) + multpv[aqprm.cell] = 1.0 + end + end extra_data_arg[:pore_volume_multiplier] = multpv end @@ -923,6 +935,18 @@ function parse_reservoir(data_file; zcorn_depths = true) 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) + if !isnothing(aquifers) + for (aq_id, aqprm) in pairs(aquifers) + # Set satnum, pvtnum, static props and verify that cell is present. + cell = aqprm.cell + @assert cell <= nc "Numerical aquifer with id $aq_id exceeds number of cells $nc in mesh. Possible failure in aquifer processing." + satnum[cell] = aqprm.satnum + pvtnum[cell] = aqprm.pvtnum + perm[:, cell] .= aqprm.permeability + poro[cell] = aqprm.porosity + end + end + set_scaling_arguments!(extra_data_arg, active_ix, data_file) domain = reservoir_domain(G; @@ -951,6 +975,20 @@ function parse_reservoir(data_file; zcorn_depths = true) z = get_zcorn_cell_depths(G, grid) @. domain[:cell_centroids][3, :] = z end + + if !isnothing(aquifers) + domain[:numerical_aquifers, nothing] = aquifers + vol = domain[:volumes] + centroids = domain[:cell_centroids] + for (aq_id, aqprm) in pairs(aquifers) + cell = aqprm.cell + A = aqprm.area + L = aqprm.length + D = aqprm.depth + vol[cell] = A*L + centroids[3, cell] = D + end + end return domain end diff --git a/src/utils.jl b/src/utils.jl index 9867a1e4..9f0751da 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1630,11 +1630,28 @@ function reservoir_transmissibility(d::DataDomain; version = :xyz) tm = d[:transmissibility_multiplier] @. T *= tm end + num_aquifer_faces = 0 + if haskey(d, :numerical_aquifers) + aquifers = d[:numerical_aquifers] + bnd_areas = d[:boundary_areas] + for (aq_id, aqprm) in pairs(aquifers) + for (bface, face) in zip(aqprm.boundary_faces, aqprm.added_faces) + A = bnd_areas[bface] + + num_aquifer_faces += 1 + end + end + error("Implementation not finished.") + end + if haskey(d, :nnc) nnc = d[:nnc] num_nnc = length(nnc) + # Aquifers come at the end, and NNC are just before the aquifer faces. + # TODO: Do this in a less brittle way, e.g. by tags. + offset = nf - num_nnc - num_aquifer_faces for (i, ncon) in enumerate(nnc) - T[nf-num_nnc+i] = ncon[7] + T[i + offset] = ncon[7] end end return T From 9aca81acb5a596f136c30c3c4c1f4ad70c6c9711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 17 Sep 2024 10:58:44 +0200 Subject: [PATCH 08/24] Set aquifer transmissibilities --- src/utils.jl | 68 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 9f0751da..fe04bccf 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1630,18 +1630,29 @@ function reservoir_transmissibility(d::DataDomain; version = :xyz) tm = d[:transmissibility_multiplier] @. T *= tm end - num_aquifer_faces = 0 if haskey(d, :numerical_aquifers) aquifers = d[:numerical_aquifers] bnd_areas = d[:boundary_areas] - for (aq_id, aqprm) in pairs(aquifers) - for (bface, face) in zip(aqprm.boundary_faces, aqprm.added_faces) - A = bnd_areas[bface] + bnd_centroids = d[:boundary_centroids] + cell_centroids = d[:cell_centroids] + D = size(cell_centroids, 1) + point_t = SVector{D, eltype(cell_centroids)} - num_aquifer_faces += 1 - end + if haskey(d, :net_to_gross) + ntg = d[:net_to_gross] + else + ntg = ones(nc) end - error("Implementation not finished.") + T, num_aquifer_faces = set_aquifer_transmissibilities!( + T, + g, d[:permeability], ntg, + aquifers, + reinterpret(point_t, cell_centroids), + reinterpret(point_t, bnd_centroids), + bnd_areas + ) + else + num_aquifer_faces = 0 end if haskey(d, :nnc) @@ -1657,6 +1668,49 @@ function reservoir_transmissibility(d::DataDomain; version = :xyz) return T end +function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_centroids, bnd_centroids, bnd_areas) + num_aquifer_faces = 0 + for (aq_id, aqprm) in pairs(aquifers) + cell = aqprm.cell + R = aqprm.length/2.0 + for (bface, face, opt, tmult) in zip( + aqprm.boundary_faces, + aqprm.added_faces, + aqprm.trans_option, + aqprm.boundary_transmult + ) + area_reservoir = bnd_areas[bface] + dist = norm(bnd_centroids[bface] - cell_centroids[cell]) + num_aquifer_faces += 1 + is_vertical = mesh_entity_has_tag(mesh, BoundaryFaces(), :orientation, :vertical, bface) + + if mesh_entity_has_tag(mesh, BoundaryFaces(), :ijk_orientation, :j, bface) + dir = 2 + elseif mesh_entity_has_tag(mesh, BoundaryFaces(), :ijk_orientation, :k, bface) + dir = 3 + else + dir = 1 + end + if is_vertical + ntg_face = ntg[cell] + else + ntg_face = 1.0 + end + T_reservoir = perm[dir, cell]*area_reservoir*ntg_face/dist + + if opt == 0 + area_aquifer = aqprm.area + else + @assert opt == 1 "Option for aquifer transmissibility expected to be 1 or 0, was $opt" + area_aquifer = area_reservoir + end + T_aquifer = area_aquifer*aqprm.permeability/R + T[face] = 1.0/(1.0/T_reservoir + 1.0/T_aquifer) + end + end + return (T, num_aquifer_faces) +end + function get_ijk_face_dir(g, N) nf = number_of_faces(g) face_dir = ones(Int, nf) From 9770a125c33eecb05ae9999af0ca5afabf1e4d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 17 Sep 2024 11:29:10 +0200 Subject: [PATCH 09/24] Init for aquifers --- src/input_simulation/data_input.jl | 53 ++++++++++++++++++++++++++++++ src/utils.jl | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 54a5e2d5..3ecba9f7 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -581,6 +581,7 @@ end function parse_state0(model, datafile; normalize = true) rmodel = reservoir_model(model) + reservoir = reservoir_domain(rmodel) init = Dict{Symbol, Any}() sol = datafile["SOLUTION"] @@ -589,6 +590,11 @@ function parse_state0(model, datafile; normalize = true) else init = parse_state0_direct_assignment(rmodel, datafile) end + if haskey(reservoir, :numerical_aquifers) + # Aquifers are a special case + initialize_numerical_aquifers!(init, rmodel, reservoir[:numerical_aquifers]) + end + if haskey(init, :Temperature) # Temperature can be set during equil, write it to data domain for # parameter initialization. @@ -736,6 +742,53 @@ function parse_state0_direct_assignment(model, datafile) return init end +function initialize_numerical_aquifers!(init, rmodel, aquifers) + p = init[:Pressure] + sys = rmodel.system + if haskey(init, :OverallMoleFractions) + # Compositional + cnames = lowercase.(component_names(sys)) + pos = findfirst(isequal("h2o"), cnames) + if isnothing(pos) + pos = findfirst(isequal("water"), cnames) + end + if isnothing(pos) + jutul_message("Did not find water component for aquifers, will not change composition", color = :yellow) + else + z = init[:OverallMoleFractions] + for (id, aquifer) in pairs(aquifers) + cell = aquifer.cell + @. z[:, cell] = 0.0 + z[pos, cell] = 1.0 + end + end + elseif haskey(init, :ImmiscibleSaturation) + # Black-oil + sw = init[:ImmiscibleSaturation] + for (id, aquifer) in pairs(aquifers) + cell = aquifer.cell + sw[cell] = 1.0 + end + else + # Immiscible model + ix = findfirst(isequal(AqueousPhase()), get_phases(sys)) + if isnothing(ix) + s = init[:Saturations] + for (id, aquifer) in pairs(aquifers) + cell = aquifer.cell + s[:, cell] = 0.0 + s[ix, cell] = 1.0 + end + end + end + + for (id, aquifer) in pairs(aquifers) + cell = aquifer.cell + p[cell] = aquifer.pressure + end + return init +end + function parse_reservoir(data_file; zcorn_depths = true) grid = data_file["GRID"] cartdims = grid["cartDims"] diff --git a/src/utils.jl b/src/utils.jl index fe04bccf..3b483cfd 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1705,7 +1705,7 @@ function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_cent area_aquifer = area_reservoir end T_aquifer = area_aquifer*aqprm.permeability/R - T[face] = 1.0/(1.0/T_reservoir + 1.0/T_aquifer) + T[face] = tmult/(1.0/T_reservoir + 1.0/T_aquifer) end end return (T, num_aquifer_faces) From b2f5e031fe6745d156717cb810b3c298e0fdd25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 17 Sep 2024 12:03:02 +0200 Subject: [PATCH 10/24] Fix to init for compositional models without oil/gas kw --- src/init/init.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 4c0d75bc..2bde63d5 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -261,15 +261,20 @@ end function parse_state0_equil(model, datafile; normalize = :sum) + sys = model.system + d = model.data_domain + has_water = haskey(datafile["RUNSPEC"], "WATER") - has_oil = haskey(datafile["RUNSPEC"], "OIL") - has_gas = haskey(datafile["RUNSPEC"], "GAS") + if sys isa CompositionalSystem + has_oil = has_gas = true + else + has_oil = haskey(datafile["RUNSPEC"], "OIL") + has_gas = haskey(datafile["RUNSPEC"], "GAS") + end is_co2 = haskey(datafile["RUNSPEC"], "JUTUL_CO2BRINE") is_single_phase = (has_water + has_oil + has_gas) == 1 - sys = model.system - d = model.data_domain has_sat_reg = haskey(d, :satnum) ncells = number_of_cells(d) if has_sat_reg From 028c644d8b453eaf937bf993e636646fcb9101e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 17 Sep 2024 12:18:59 +0200 Subject: [PATCH 11/24] Improve transmissibility for aquifers, add handling of NaN --- src/input_simulation/data_input.jl | 5 ++++- src/utils.jl | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 3ecba9f7..0daa363d 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -784,7 +784,10 @@ function initialize_numerical_aquifers!(init, rmodel, aquifers) for (id, aquifer) in pairs(aquifers) cell = aquifer.cell - p[cell] = aquifer.pressure + pa = aquifer.pressure + if isfinite(pa) && pa > DEFAULT_MINIMUM_PRESSURE + p[cell] = pa + end end return init end diff --git a/src/utils.jl b/src/utils.jl index 3b483cfd..1ffd62f6 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1048,6 +1048,11 @@ function setup_reservoir_state(rmodel::SimulationModel; kwarg...) res_init = Dict{Symbol, Any}() for (k, v) in kwarg I = findfirst(isequal(k), pvars) + if eltype(v)<:AbstractFloat + if !all(isfinite, v) + jutul_message("setup_reservoir_state", "Non-finite entries found in initializer for $k.", color = :red) + end + end if isnothing(I) if !(k in svars) jutul_message("setup_reservoir_state", "Recieved primary variable $k, but this is not known to reservoir model.") @@ -1649,6 +1654,7 @@ function reservoir_transmissibility(d::DataDomain; version = :xyz) aquifers, reinterpret(point_t, cell_centroids), reinterpret(point_t, bnd_centroids), + g.boundary_faces.neighbors, bnd_areas ) else @@ -1668,10 +1674,10 @@ function reservoir_transmissibility(d::DataDomain; version = :xyz) return T end -function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_centroids, bnd_centroids, bnd_areas) +function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_centroids, bnd_centroids, bnd_neighbors, bnd_areas) num_aquifer_faces = 0 for (aq_id, aqprm) in pairs(aquifers) - cell = aqprm.cell + aquifer_cell = aqprm.cell R = aqprm.length/2.0 for (bface, face, opt, tmult) in zip( aqprm.boundary_faces, @@ -1680,7 +1686,8 @@ function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_cent aqprm.boundary_transmult ) area_reservoir = bnd_areas[bface] - dist = norm(bnd_centroids[bface] - cell_centroids[cell]) + reservoir_cell = bnd_neighbors[bface] + dist = norm(bnd_centroids[bface] - cell_centroids[reservoir_cell]) num_aquifer_faces += 1 is_vertical = mesh_entity_has_tag(mesh, BoundaryFaces(), :orientation, :vertical, bface) @@ -1692,11 +1699,11 @@ function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_cent dir = 1 end if is_vertical - ntg_face = ntg[cell] + ntg_face = ntg[reservoir_cell] else ntg_face = 1.0 end - T_reservoir = perm[dir, cell]*area_reservoir*ntg_face/dist + T_reservoir = perm[dir, reservoir_cell]*area_reservoir*ntg_face/dist if opt == 0 area_aquifer = aqprm.area @@ -1705,7 +1712,13 @@ function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_cent area_aquifer = area_reservoir end T_aquifer = area_aquifer*aqprm.permeability/R - T[face] = tmult/(1.0/T_reservoir + 1.0/T_aquifer) + effective_trans = tmult/(1.0/T_reservoir + 1.0/T_aquifer) + if isfinite(effective_trans) + T[face] = effective_trans + else + @error "Non-finite aquifer transmissibility for numerical aquifer $aq_id, setting to zero" T_aquifer T_reservoir aqprm + T[face] = 0.0 + end end end return (T, num_aquifer_faces) From 0fee42c85f3e6a83c1c96a5fb1ae5b290e1095a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 17 Sep 2024 12:26:39 +0200 Subject: [PATCH 12/24] Improve kr region printing --- src/variables/relperm/advanced.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/variables/relperm/advanced.jl b/src/variables/relperm/advanced.jl index b9a09260..c701351d 100644 --- a/src/variables/relperm/advanced.jl +++ b/src/variables/relperm/advanced.jl @@ -293,7 +293,8 @@ function Base.show(io::IO, t::MIME"text/plain", kr::ReservoirRelativePermeabilit if isnothing(kr.regions) println(io, "\n regions: No regions defined.") else - println(io, "\n regions: $(unique(kr.regions)...).") + regstr = join(unique(kr.regions), ", ") + println(io, "\n regions: $regstr.") end println(io, "\n scaling: $(endpoint_scaling_model(kr))") end From a0468e6e253f2a958fb12f004bd2eb1a8dd15252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Tue, 17 Sep 2024 13:10:52 +0200 Subject: [PATCH 13/24] Avoid out of bounds imbibition tables Shouldn't really happen, but can occur in some input cases --- src/variables/relperm/advanced.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/variables/relperm/advanced.jl b/src/variables/relperm/advanced.jl index c701351d..7d4dadcb 100644 --- a/src/variables/relperm/advanced.jl +++ b/src/variables/relperm/advanced.jl @@ -401,11 +401,14 @@ Base.@propagate_inbounds @inline function update_two_phase_relperm!(kr, relperm, end function imbibition_table_by_region(f, reg) - if length(f) == 1 + nkr = length(f) + if nkr == 1 @assert reg == 1 ix = 1 else - ix = reg + (length(f) ÷ 2) + # Don't go outside table range. In the case of values beyond the range, + # some cells may use imbibiton for both curves. + ix = min(reg + (nkr ÷ 2), nkr) end return f[ix] end From 7f6db4359873806ef0896f7292b4cd3b4f3940b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 18 Sep 2024 21:10:31 +0200 Subject: [PATCH 14/24] Add more checking to reservoir_domain --- src/utils.jl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 1ffd62f6..694fa419 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -76,6 +76,7 @@ function reservoir_domain(g; all(isfinite, diffusion) || throw(ArgumentError("Keyword argument diffusion has non-finite entries.")) kwarg = (diffusion = diffusion, kwarg...) end + minimum(permeability) >= 0 || throw(ArgumentError("All permeability values must be non-negative.")) nk = length(permeability) nc = number_of_cells(g) if nk != nc && permeability isa AbstractVector @@ -84,7 +85,8 @@ function reservoir_domain(g; permeability = repeat(permeability, 1, nc) end end - return DataDomain(g; + + reservoir = DataDomain(g; permeability = permeability, porosity = porosity, rock_thermal_conductivity = rock_thermal_conductivity, @@ -92,6 +94,13 @@ function reservoir_domain(g; rock_density = rock_density, kwarg... ) + for k in [:porosity, :net_to_gross] + if haskey(reservoir, k) + val = reservoir[k] + minimum(val) > 0 || throw(ArgumentError("Keyword argument $k must have positive entries.")) + end + end + return reservoir end """ From 4d65ce5b61f067eb4de9977080af49444f870376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 18 Sep 2024 21:10:40 +0200 Subject: [PATCH 15/24] Don't let NTG be set to zero for aquifers --- src/input_simulation/data_input.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index 0daa363d..ae5b1478 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -1001,6 +1001,15 @@ function parse_reservoir(data_file; zcorn_depths = true) perm[:, cell] .= aqprm.permeability poro[cell] = aqprm.porosity end + for (k, v) in extra_data_arg + if k == :net_to_gross + for (aq_id, aqprm) in pairs(aquifers) + cell = aqprm.cell + # Net to gross should not be set for aquifers? + v[cell] = 1.0 + end + end + end end set_scaling_arguments!(extra_data_arg, active_ix, data_file) From e83244450679b77bec8eac93626fa1de33562fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 18 Sep 2024 22:53:30 +0200 Subject: [PATCH 16/24] Unify treatment of reference phase index for Pc --- src/init/init.jl | 28 +--------------------------- src/multiphase.jl | 38 ++++++++++++++++++++++++++++++++++++-- src/types.jl | 29 +++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 2bde63d5..6d030efe 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -152,7 +152,7 @@ function equilibriate_state!(init, depths, model, sys, contacts, depth, datum_pr return phase_density end # Find the reference phase. It is either liquid - ref_phase = find_reference_phase(model.system) + ref_phase = get_reference_phase_index(model.system) pressures = determine_hydrostatic_pressures(depths, depth, zmin, zmax, contacts, datum_pressure, density_f, contacts_pc, ref_phase) if nph > 1 relperm = model.secondary_variables[:RelativePermeabilities] @@ -234,32 +234,6 @@ function equilibriate_state!(init, depths, model, sys, contacts, depth, datum_pr return init end -function find_reference_phase(system) - mphases = get_phases(system) - function find_phase(k) - ix = 0 - for (i, ph) in enumerate(mphases) - if ph isa LiquidPhase - ix = i - break - end - end - return ix - end - l = find_phase(LiquidPhase) - a = find_phase(AqueousPhase) - if l > 0 - phase = l - elseif a > 0 - phase = a - else - # Last phase is water or something custom, send out 1. - phase = 1 - end - return phase -end - - function parse_state0_equil(model, datafile; normalize = :sum) sys = model.system d = model.data_domain diff --git a/src/multiphase.jl b/src/multiphase.jl index 48ed555a..3dced3e4 100644 --- a/src/multiphase.jl +++ b/src/multiphase.jl @@ -495,12 +495,46 @@ end function capillary_pressure(model, s) pck = :CapillaryPressure + ref_index = get_reference_phase_index(model.system) if haskey(s, pck) pc = s[pck] - ref_index = min(2, number_of_phases(model.system)) else pc = nothing - ref_index = 1 end return(pc, ref_index) end + +get_reference_phase_index(::SinglePhaseSystem) = 1 + +function get_reference_phase_index(system::JutulSystem) + mphases = get_phases(system) + return get_reference_phase_index(mphases) +end + +function get_reference_phase_index(sys::MultiPhaseSystem) + return sys.reference_phase_index +end + +function get_reference_phase_index(mphases) + function find_phase(k) + ix = 0 + for (i, ph) in enumerate(mphases) + if ph isa LiquidPhase + ix = i + break + end + end + return ix + end + l = find_phase(LiquidPhase) + a = find_phase(AqueousPhase) + if l > 0 + phase = l + elseif a > 0 + phase = a + else + # Last phase is water or something custom, send out 1. + phase = 1 + end + return phase +end diff --git a/src/types.jl b/src/types.jl index 6822e6c5..d5de5c50 100644 --- a/src/types.jl +++ b/src/types.jl @@ -24,6 +24,7 @@ struct MultiPhaseCompositionalSystemLV{E, T, O, R, N} <: CompositionalSystem whe components::Vector{String} equation_of_state::E rho_ref::R + reference_phase_index::Int end const LVCompositional2PhaseSystem = MultiPhaseCompositionalSystemLV{<:Any, <:Any, Nothing, <:Any, <:Any} @@ -43,7 +44,13 @@ the phase that is not a liquid or a vapor phase will be treated as immiscible in subsequent simulations and given the name from `other_name` when listed as a component. """ -function MultiPhaseCompositionalSystemLV(equation_of_state, phases = (LiquidPhase(), VaporPhase()); reference_densities = ones(length(phases)), other_name = "Water") +function MultiPhaseCompositionalSystemLV( + equation_of_state, + phases = (LiquidPhase(), VaporPhase()); + reference_densities = ones(length(phases)), + other_name = "Water", + reference_phase_index = get_reference_phase_index(phases) + ) c = copy(MultiComponentFlash.component_names(equation_of_state)) N = length(c) phases = tuple(phases...) @@ -61,7 +68,7 @@ function MultiPhaseCompositionalSystemLV(equation_of_state, phases = (LiquidPhas end only(findall(isequal(LiquidPhase()), phases)) only(findall(isequal(VaporPhase()), phases)) - return MultiPhaseCompositionalSystemLV{typeof(equation_of_state), T, O, typeof(reference_densities), N}(phases, c, equation_of_state, reference_densities) + return MultiPhaseCompositionalSystemLV{typeof(equation_of_state), T, O, typeof(reference_densities), N}(phases, c, equation_of_state, reference_densities, reference_phase_index) end function Base.show(io::IO, sys::MultiPhaseCompositionalSystemLV) @@ -90,6 +97,7 @@ struct StandardBlackOilSystem{D, V, W, R, F, T, P, Num} <: BlackOilSystem rs_eps::Num rv_eps::Num s_eps::Num + reference_phase_index::Int end """ @@ -117,13 +125,18 @@ function StandardBlackOilSystem(; eps_s = 1e-5, eps_rs = nothing, eps_rv = nothing, - formulation::Symbol = :varswitch + formulation::Symbol = :varswitch, + reference_phase_index = missing ) rs_max = region_wrap(rs_max) rv_max = region_wrap(rv_max) RS = typeof(rs_max) RV = typeof(rv_max) phases = tuple(phases...) + if ismissing(reference_phase_index) + reference_phase_index = get_reference_phase_index(phases) + end + reference_phase_index::Int nph = length(phases) if nph == 2 && length(reference_densities) == 3 reference_densities = reference_densities[2:3] @@ -151,7 +164,7 @@ function StandardBlackOilSystem(; end end @assert formulation == :varswitch || formulation == :zg - return StandardBlackOilSystem{RS, RV, has_water, typeof(reference_densities), formulation, typeof(phase_ind), typeof(phases), Float64}(rs_max, rv_max, reference_densities, phase_ind, phases, saturated_chop, keep_bubble_flag, eps_rs, eps_rv, eps_s) + return StandardBlackOilSystem{RS, RV, has_water, typeof(reference_densities), formulation, typeof(phase_ind), typeof(phases), Float64}(rs_max, rv_max, reference_densities, phase_ind, phases, saturated_chop, keep_bubble_flag, eps_rs, eps_rv, eps_s, reference_phase_index) end @inline function rs_max_function(sys::StandardBlackOilSystem, region = 1) @@ -190,6 +203,7 @@ const StandardBlackOilModelWithWater = SimulationModel{<:Any, <:StandardBlackOil struct ImmiscibleSystem{T, F} <: MultiPhaseSystem where {T<:Tuple, F<:NTuple} phases::T rho_ref::F + reference_phase_index::Int end """ @@ -205,10 +219,13 @@ densitites. This system is easy to specify with [Pressure](@ref) and that there is no mass transfer between phases and that a phase is uniform in composition. """ -function ImmiscibleSystem(phases; reference_densities = ones(length(phases))) +function ImmiscibleSystem(phases; reference_densities = ones(length(phases)), reference_phase_index = missing) phases = tuple(phases...) + if ismissing(reference_phase_index) + reference_phase_index = get_reference_phase_index(phases) + end reference_densities = tuple(reference_densities...) - return ImmiscibleSystem(phases, reference_densities) + return ImmiscibleSystem(phases, reference_densities, reference_phase_index) end Base.show(io::IO, t::ImmiscibleSystem) = print(io, "ImmiscibleSystem with $(join([typeof(p) for p in t.phases], ", "))") From e76e8be2801e53cdce8fbad4e28e059473828021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Wed, 18 Sep 2024 23:12:22 +0200 Subject: [PATCH 17/24] Bump compat --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 73f23923..48fec3e1 100644 --- a/Project.toml +++ b/Project.toml @@ -53,7 +53,7 @@ DelimitedFiles = "1.9.1" DocStringExtensions = "0.9.3" ForwardDiff = "0.10.30" GLMakie = "0.10.0" -GeoEnergyIO = "1.1.6" +GeoEnergyIO = "1.1.7" HYPRE = "1.4.0" Jutul = "0.2.37" Krylov = "0.9.1" From 3d9e4837c68efe8b71951d76f2f31112d00d127c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 19 Sep 2024 14:58:18 +0200 Subject: [PATCH 18/24] Clean up Pc implementation a bit Don't carry signs through, just normalize on input --- src/init/init.jl | 7 ++----- src/input_simulation/mrst_input.jl | 20 ++++++-------------- src/variables/capillary.jl | 5 +++-- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/init/init.jl b/src/init/init.jl index 6d030efe..82f01d06 100644 --- a/src/init/init.jl +++ b/src/init/init.jl @@ -371,9 +371,6 @@ function parse_state0_equil(model, datafile; normalize = :sum) 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 s = s[ix] cap = cap[ix] if length(s) == 1 @@ -425,11 +422,11 @@ function parse_state0_equil(model, datafile; normalize = :sum) contacts_pc = (goc_pc, ) else contacts = (woc, ) - contacts_pc = (-woc_pc, ) + contacts_pc = (woc_pc, ) end else contacts = (woc, goc) - contacts_pc = (-woc_pc, goc_pc) + contacts_pc = (woc_pc, goc_pc) end if disgas || vapoil diff --git a/src/input_simulation/mrst_input.jl b/src/input_simulation/mrst_input.jl index b0a30d78..c5035eb6 100644 --- a/src/input_simulation/mrst_input.jl +++ b/src/input_simulation/mrst_input.jl @@ -588,7 +588,7 @@ function flat_region_expand(x::Vector, n = nothing) end function deck_pc(props; oil, water, gas, satnum = nothing, is_co2 = false) - function get_pc(T, pc_ix, reverse = false) + function get_pc(T, pc_ix; reverse = false, sgn = 1.0) found = false PC = [] for tab in T @@ -601,6 +601,7 @@ function deck_pc(props; oil, water, gas, satnum = nothing, is_co2 = false) pc = -pc[ix] s = s[ix] end + @. pc = sgn*pc s, pc = saturation_table_handle_defaults(s, pc) if length(T) == 1 constant_dx = missing @@ -616,28 +617,19 @@ function deck_pc(props; oil, water, gas, satnum = nothing, is_co2 = false) pc_impl = Vector{Any}() if water && oil if haskey(props, "SWOF") - interp_ow, found_pcow = get_pc(props["SWOF"], 4) + interp_ow, found_pcow = get_pc(props["SWOF"], 4, sgn = -1) else - interp_ow, found_pcow = get_pc(props["SWFN"], 3) + interp_ow, found_pcow = get_pc(props["SWFN"], 3, sgn = -1) end push!(pc_impl, interp_ow) else found_pcow = false end if oil && gas - # if is_co2 && (oil + gas + water) == 2 - # # CO2 store models may act was oil-gas but they are really - # # representing water-co2 and the capillary pressure should be - # # p_g = p_o + p_og - # reverse = false - # else - # reverse = !water - # end - reverse = !water if haskey(props, "SGOF") - interp_og, found_pcog = get_pc(props["SGOF"], 4, reverse) + interp_og, found_pcog = get_pc(props["SGOF"], 4) else - interp_og, found_pcog = get_pc(props["SGFN"], 3, reverse) + interp_og, found_pcog = get_pc(props["SGFN"], 3) end push!(pc_impl, interp_og) else diff --git a/src/variables/capillary.jl b/src/variables/capillary.jl index a26334f4..f54e86d6 100644 --- a/src/variables/capillary.jl +++ b/src/variables/capillary.jl @@ -72,7 +72,7 @@ end reg = region(pc.regions, c) pcow_c = table_by_region(pcow, reg) sw = Saturations[1, c] - Δp[1, c] = -pcow_c(sw) + Δp[1, c] = pcow_c(sw) Δp[2, c] = 0 end else @@ -82,7 +82,8 @@ end pcog_c = table_by_region(pcog, reg) sw = Saturations[1, c] sg = Saturations[3, c] - Δp[1, c] = -pcow_c(sw) + # Note: Negative sign already taken care of in input + Δp[1, c] = pcow_c(sw) Δp[2, c] = pcog_c(sg) end end From 4d687e0727fd1dc5b8a54258059f0626d3f27d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 19 Sep 2024 15:13:48 +0200 Subject: [PATCH 19/24] Add asserts to pc evaluator with hard coded reference phases --- src/variables/capillary.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/variables/capillary.jl b/src/variables/capillary.jl index f54e86d6..e15d0100 100644 --- a/src/variables/capillary.jl +++ b/src/variables/capillary.jl @@ -49,8 +49,10 @@ end @jutul_secondary function update_pc!(Δp, pc::SimpleCapillaryPressure, model, Saturations, ix) cap = pc.pc npc = size(Δp, 1) + reference_ph = get_reference_phase_index(model.system) if npc == 1 pcow = only(cap) + @assert reference_ph == 1 @inbounds for c in ix reg = region(pc.regions, c) pcow_c = table_by_region(pcow, reg) @@ -58,6 +60,7 @@ end Δp[1, c] = pcow_c(sw) end elseif npc == 2 + @assert reference_ph == 2 pcow, pcog = cap if isnothing(pcow) @inbounds for c in ix From 27a6ddcdd3f48beb3d6d960d9c252147de155f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Thu, 19 Sep 2024 20:26:45 +0200 Subject: [PATCH 20/24] Generalize pc evaluator --- src/variables/capillary.jl | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/variables/capillary.jl b/src/variables/capillary.jl index e15d0100..e21c71b8 100644 --- a/src/variables/capillary.jl +++ b/src/variables/capillary.jl @@ -49,24 +49,38 @@ end @jutul_secondary function update_pc!(Δp, pc::SimpleCapillaryPressure, model, Saturations, ix) cap = pc.pc npc = size(Δp, 1) + nph = size(Saturations, 1) + @assert npc == nph - 1 reference_ph = get_reference_phase_index(model.system) if npc == 1 + if reference_ph == 1 + w = 2 + else + w = 1 + end pcow = only(cap) @assert reference_ph == 1 @inbounds for c in ix reg = region(pc.regions, c) pcow_c = table_by_region(pcow, reg) - sw = Saturations[1, c] + sw = Saturations[w, c] Δp[1, c] = pcow_c(sw) end elseif npc == 2 - @assert reference_ph == 2 + if reference_ph == 1 + w, g = 2, 3 + elseif reference_ph == 2 + w, g = 1, 3 + else + @assert reference_ph == 3 + w, g = 1, 2 + end pcow, pcog = cap if isnothing(pcow) @inbounds for c in ix reg = region(pc.regions, c) pcog_c = table_by_region(pcog, reg) - sg = Saturations[3, c] + sg = Saturations[g, c] Δp[1, c] = 0 Δp[2, c] = pcog_c(sg) end @@ -74,7 +88,7 @@ end @inbounds for c in ix reg = region(pc.regions, c) pcow_c = table_by_region(pcow, reg) - sw = Saturations[1, c] + sw = Saturations[w, c] Δp[1, c] = pcow_c(sw) Δp[2, c] = 0 end @@ -83,8 +97,8 @@ end reg = region(pc.regions, c) pcow_c = table_by_region(pcow, reg) pcog_c = table_by_region(pcog, reg) - sw = Saturations[1, c] - sg = Saturations[3, c] + sw = Saturations[w, c] + sg = Saturations[g, c] # Note: Negative sign already taken care of in input Δp[1, c] = pcow_c(sw) Δp[2, c] = pcog_c(sg) From 6af8e813819c5f1c611ac6173161e35e210572a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 21 Sep 2024 14:39:12 +0200 Subject: [PATCH 21/24] Support for multiple cells in numerical aquifers --- src/input_simulation/data_input.jl | 64 +++++++++++++++++------------- src/utils.jl | 27 ++++++++++--- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index ae5b1478..f50a1628 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -766,8 +766,8 @@ function initialize_numerical_aquifers!(init, rmodel, aquifers) # Black-oil sw = init[:ImmiscibleSaturation] for (id, aquifer) in pairs(aquifers) - cell = aquifer.cell - sw[cell] = 1.0 + cells = map(x -> x.cell, aquifer.aquifer_cells) + sw[cells] .= 1.0 end else # Immiscible model @@ -775,18 +775,20 @@ function initialize_numerical_aquifers!(init, rmodel, aquifers) if isnothing(ix) s = init[:Saturations] for (id, aquifer) in pairs(aquifers) - cell = aquifer.cell - s[:, cell] = 0.0 - s[ix, cell] = 1.0 + cells = map(x -> x.cell, aquifer.aquifer_cells) + s[:, cells] .= 0.0 + s[ix, cells] .= 1.0 end end end for (id, aquifer) in pairs(aquifers) - cell = aquifer.cell - pa = aquifer.pressure - if isfinite(pa) && pa > DEFAULT_MINIMUM_PRESSURE - p[cell] = pa + for aqprm in aquifer.aquifer_cells + cell = aqprm.cell + pa = aqprm.pressure + if isfinite(pa) && pa > DEFAULT_MINIMUM_PRESSURE + p[cell] = pa + end end end return init @@ -992,21 +994,25 @@ function parse_reservoir(data_file; zcorn_depths = true) eqlnum = GeoEnergyIO.InputParser.get_data_file_cell_region(data_file, :eqlnum, active = active_ix) if !isnothing(aquifers) - for (aq_id, aqprm) in pairs(aquifers) - # Set satnum, pvtnum, static props and verify that cell is present. - cell = aqprm.cell - @assert cell <= nc "Numerical aquifer with id $aq_id exceeds number of cells $nc in mesh. Possible failure in aquifer processing." - satnum[cell] = aqprm.satnum - pvtnum[cell] = aqprm.pvtnum - perm[:, cell] .= aqprm.permeability - poro[cell] = aqprm.porosity + for (aq_id, aquifer) in pairs(aquifers) + for aqprm in aquifer.aquifer_cells + # Set satnum, pvtnum, static props and verify that cell is present. + cell = aqprm.cell + @assert cell <= nc "Numerical aquifer with id $aq_id exceeds number of cells $nc in mesh. Possible failure in aquifer processing." + satnum[cell] = aqprm.satnum + pvtnum[cell] = aqprm.pvtnum + perm[:, cell] .= aqprm.permeability + poro[cell] = aqprm.porosity + end end for (k, v) in extra_data_arg if k == :net_to_gross - for (aq_id, aqprm) in pairs(aquifers) - cell = aqprm.cell - # Net to gross should not be set for aquifers? - v[cell] = 1.0 + for (aq_id, aquifer) in pairs(aquifers) + for aqprm in aquifer.aquifer_cells + cell = aqprm.cell + # Net to gross should not be set for aquifers? + v[cell] = 1.0 + end end end end @@ -1045,13 +1051,15 @@ function parse_reservoir(data_file; zcorn_depths = true) domain[:numerical_aquifers, nothing] = aquifers vol = domain[:volumes] centroids = domain[:cell_centroids] - for (aq_id, aqprm) in pairs(aquifers) - cell = aqprm.cell - A = aqprm.area - L = aqprm.length - D = aqprm.depth - vol[cell] = A*L - centroids[3, cell] = D + for (aq_id, aquifer) in pairs(aquifers) + for aqprm in aquifer.aquifer_cells + cell = aqprm.cell + A = aqprm.area + L = aqprm.length + D = aqprm.depth + vol[cell] = A*L + centroids[3, cell] = D + end end end return domain diff --git a/src/utils.jl b/src/utils.jl index 694fa419..7831b153 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1685,15 +1685,17 @@ end function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_centroids, bnd_centroids, bnd_neighbors, bnd_areas) num_aquifer_faces = 0 - for (aq_id, aqprm) in pairs(aquifers) + # Connections to the reservoir + for (aq_id, aquifer) in pairs(aquifers) + aqprm = aquifer.aquifer_cells[1] aquifer_cell = aqprm.cell R = aqprm.length/2.0 for (bface, face, opt, tmult) in zip( - aqprm.boundary_faces, - aqprm.added_faces, - aqprm.trans_option, - aqprm.boundary_transmult - ) + aquifer.boundary_faces, + aquifer.added_faces, + aquifer.trans_option, + aquifer.boundary_transmult + ) area_reservoir = bnd_areas[bface] reservoir_cell = bnd_neighbors[bface] dist = norm(bnd_centroids[bface] - cell_centroids[reservoir_cell]) @@ -1730,6 +1732,19 @@ function set_aquifer_transmissibilities!(T, mesh, perm, ntg, aquifers, cell_cent end end end + # Aquifer internal connections + for (aq_id, aquifer) in pairs(aquifers) + aqprms = aquifer.aquifer_cells + @assert length(aquifer.aquifer_faces) == length(aqprms)-1 + for (i, face) in enumerate(aquifer.aquifer_faces) + num_aquifer_faces += 1 + curr = aqprms[i] + next = aqprms[i+1] + T_c = curr.area*curr.permeability/(curr.length/2.0) + T_n = next.area*next.permeability/(next.length/2.0) + T[face] = 1.0/(1.0/T_c + 1.0/T_n) + end + end return (T, num_aquifer_faces) end From 1891d4db4543869e4f9ba78028a89ab0a0abc253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sat, 21 Sep 2024 14:40:23 +0200 Subject: [PATCH 22/24] Fix for compositional aquifers --- src/input_simulation/data_input.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/input_simulation/data_input.jl b/src/input_simulation/data_input.jl index f50a1628..a2ef54eb 100644 --- a/src/input_simulation/data_input.jl +++ b/src/input_simulation/data_input.jl @@ -757,9 +757,9 @@ function initialize_numerical_aquifers!(init, rmodel, aquifers) else z = init[:OverallMoleFractions] for (id, aquifer) in pairs(aquifers) - cell = aquifer.cell - @. z[:, cell] = 0.0 - z[pos, cell] = 1.0 + cells = map(x -> x.cell, aquifer.aquifer_cells) + @. z[:, cells] = 0.0 + @. z[pos, cells] = 1.0 end end elseif haskey(init, :ImmiscibleSaturation) From 27bc6469c947230b1f2276688ce83c52396a19bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sun, 22 Sep 2024 17:43:51 +0200 Subject: [PATCH 23/24] Update co2_sloped.jl --- examples/co2_sloped.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/co2_sloped.jl b/examples/co2_sloped.jl index 3142c9c4..a8df1a83 100644 --- a/examples/co2_sloped.jl +++ b/examples/co2_sloped.jl @@ -201,9 +201,9 @@ println("Boundary condition added to $(length(bc)) cells.") # ## Plot the model plot_reservoir(model) # ## Set up schedule -# We set up 25 years of injection and 1000 years of migration where the well is -# shut. The density of the injector is set to 900 kg/m^3, which is roughly -# the density of CO2 at in-situ conditions. +# We set up 25 years of injection and 475 years of migration where the well is +# shut. The density of the injector is set to 900 kg/m^3, which is roughly the +# density of CO2 at these high-pressure in-situ conditions. nstep = 25 nstep_shut = 475 dt_inject = fill(365.0day, nstep) @@ -240,10 +240,10 @@ wd, states, t = simulate_reservoir(state0, model, dt, max_timestep = 90day ) # ## Plot the CO2 mole fraction -# We plot log10 of the CO2 mole fraction. We use log10 to account for the fact -# that the mole fraction in cells made up of only the aqueous phase is much -# smaller than that of cells with only the gaseous phase, where there is almost -# just CO2. +# We plot the overall CO2 mole fraction. We scale the color range to account for +# the fact that the mole fraction in cells made up of only the aqueous phase is +# much smaller than that of cells with only the gaseous phase, where there is +# almost just CO2. using GLMakie function plot_co2!(fig, ix, x, title = "") ax = Axis3(fig[ix, 1], @@ -252,7 +252,7 @@ function plot_co2!(fig, ix, x, title = "") elevation = 0.05, aspect = (1.0, 1.0, 0.3), title = title) - plt = plot_cell_data!(ax, mesh, x, colormap = :seaborn_icefire_gradient) + plt = plot_cell_data!(ax, mesh, x, colormap = :seaborn_icefire_gradient, colorrange = (0.0, 0.1)) Colorbar(fig[ix, 2], plt) end fig = Figure(size = (900, 1200)) @@ -260,7 +260,7 @@ for (i, step) in enumerate([1, 5, nstep, nstep+nstep_shut]) if use_immiscible plot_co2!(fig, i, states[step][:Saturations][2, :], "CO2 plume saturation at report step $step/$(nstep+nstep_shut)") else - plot_co2!(fig, i, log10.(states[step][:OverallMoleFractions][2, :]), "log10 of CO2 mole fraction at report step $step/$(nstep+nstep_shut)") + plot_co2!(fig, i, states[step][:OverallMoleFractions][2, :], "CO2 mole fraction at report step $step/$(nstep+nstep_shut)") end end fig From 72b720656202dd47d7277aff1eadbc222729e7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olav=20M=C3=B8yner?= Date: Sun, 22 Sep 2024 17:43:57 +0200 Subject: [PATCH 24/24] Bump version --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 48fec3e1..a4896d5f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "JutulDarcy" uuid = "82210473-ab04-4dce-b31b-11573c4f8e0a" authors = ["Olav Møyner "] -version = "0.2.30" +version = "0.2.31" [deps] Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" @@ -53,7 +53,7 @@ DelimitedFiles = "1.9.1" DocStringExtensions = "0.9.3" ForwardDiff = "0.10.30" GLMakie = "0.10.0" -GeoEnergyIO = "1.1.7" +GeoEnergyIO = "1.1.9" HYPRE = "1.4.0" Jutul = "0.2.37" Krylov = "0.9.1"