Skip to content

Norne: Real field black-oil model

The Norne model is a real field model. The model has been adapted so that the input file only contains features present in JutulDarcy, with the most notable omissions being removal of hysteresis and threshold pressures between equilibriation reqgions. For more details, see the OPM data webpage

julia
using Jutul, JutulDarcy, GLMakie, DelimitedFiles, HYPRE, GeoEnergyIO
norne_dir = GeoEnergyIO.test_input_file_path("NORNE_NOHYST")
data_pth = joinpath(norne_dir, "NORNE_NOHYST.DATA")
data = parse_data_file(data_pth)
case = setup_case_from_data_file(data);
F-4H completion: Removed COMPDAT as (36, 68, 17) is not active in processed mesh.
Rel. Perm. Scaling: Three-point scaling active.
Shutting D-1H: Well has no open perforations at step 137, shutting.
┌ Warning: Negative saturation in 215 cells for phase 2. Normalizing.
└ @ JutulDarcy ~/work/JutulDarcy.jl/JutulDarcy.jl/src/init/init.jl:787
Transmissibility: Replaced 2 non-finite half-transmissibilities (out of 267694, 0.0%) with zero.
Transmissibility: Replaced 2 non-finite half-transmissibilities (out of 267694, 0.0%) with zero.

Unpack the case to see basic data structures

julia
model = case.model
parameters = case.parameters
forces = case.forces
dt = case.dt;

Plot the reservoir mesh, wells and faults

We compose a few different plotting calls together to make a plot that shows the outline of the mesh, the fault structures and the well trajectories.

julia
import Jutul: plot_mesh_edges!
reservoir = reservoir_domain(model)
mesh = physical_representation(reservoir)
wells = get_model_wells(model)
fig = Figure(size = (1200, 800))
ax = Axis3(fig[1, 1], zreversed = true)
plot_mesh_edges!(ax, mesh, alpha = 0.5)
for (k, w) in wells
    plot_well!(ax, mesh, w)
end
plot_faults!(ax, mesh, alpha = 0.5)
ax.azimuth[] = -3.0
ax.elevation[] = 0.5
fig

Plot the reservoir static properties in interactive viewer

julia
fig = plot_reservoir(model, key = :porosity)
ax = fig.current_axis[]
plot_faults!(ax, mesh, alpha = 0.5)
ax.azimuth[] = -3.0
ax.elevation[] = 0.5
fig

Simulate the model

julia
ws, states = simulate_reservoir(case, output_substates = true)
ReservoirSimResult with 463 entries:

  wells (36 present):
    :B-4H
    :F-4H
    :D-4H
    :F-2H
    :D-2H
    :B-1H
    :C-4H
    :C-1H
    :B-2H
    :E-1H
    :B-4BH
    :D-3AH
    :D-3BH
    :C-3H
    :E-3H
    :K-3H
    :E-4AH
    :D-1H
    :B-1AH
    :E-3CH
    :E-4H
    :E-2H
    :E-2AH
    :C-4AH
    :B-1BH
    :C-2H
    :B-4DH
    :D-3H
    :E-3AH
    :D-4AH
    :B-3H
    :F-3H
    :E-3BH
    :D-1CH
    :F-1H
    :B-4AH
    Results per well:
       :wrat => Vector{Float64} of size (463,)
       :Aqueous_mass_rate => Vector{Float64} of size (463,)
       :orat => Vector{Float64} of size (463,)
       :bhp => Vector{Float64} of size (463,)
       :lrat => Vector{Float64} of size (463,)
       :mass_rate => Vector{Float64} of size (463,)
       :rate => Vector{Float64} of size (463,)
       :Vapor_mass_rate => Vector{Float64} of size (463,)
       :control => Vector{Symbol} of size (463,)
       :Liquid_mass_rate => Vector{Float64} of size (463,)
       :grat => Vector{Float64} of size (463,)

  states (Vector with 463 entries, reservoir variables for each state)
    :Rv => Vector{Float64} of size (44417,)
    :BlackOilUnknown => Vector{BlackOilX{Float64}} of size (44417,)
    :Saturations => Matrix{Float64} of size (3, 44417)
    :Pressure => Vector{Float64} of size (44417,)
    :Rs => Vector{Float64} of size (44417,)
    :ImmiscibleSaturation => Vector{Float64} of size (44417,)
    :TotalMasses => Matrix{Float64} of size (3, 44417)

  time (report time for each state)
     Vector{Float64} of length 463

  result (extended states, reports)
     SimResult with 247 entries

  extra
     Dict{Any, Any} with keys :simulator, :config

  Completed at Oct. 01 2024 16:10 after 11 minutes, 16 seconds, 842.8 milliseconds.

Plot the reservoir solution

julia
fig = plot_reservoir(model, states, step = 247, key = :Saturations)
ax = fig.current_axis[]
ax.azimuth[] = -3.0
ax.elevation[] = 0.5
fig

Load reference and set up plotting

julia
csv_path = joinpath(norne_dir, "REFERENCE.CSV")
data, header = readdlm(csv_path, ',', header = true)
time_ref = data[:, 1]
time_jutul = deepcopy(ws.time)
wells = deepcopy(ws.wells)
wnames = collect(keys(wells))
nw = length(wnames)
day = si_unit(:day)
cmap = :tableau_hue_circle

inj = Symbol[]
prod = Symbol[]
for (wellname, well) in pairs(wells)
    qts = well[:wrat] + well[:orat] + well[:grat]
    if sum(qts) > 0
        push!(inj, wellname)
    else
        push!(prod, wellname)
    end
end

function plot_well_comparison(response, well_names, reponse_name = "$response")
    fig = Figure(size = (1000, 400))
    if response == :bhp
        ys = 1/si_unit(:bar)
        yl = "Bottom hole pressure / Bar"
    elseif response == :wrat
        ys = si_unit(:day)
        yl = "Surface water rate / m³/day"
    elseif response == :grat
        ys = si_unit(:day)/1e6
        yl = "Surface gas rate / 10⁶ m³/day"
    elseif response == :orat
        ys = si_unit(:day)/(1000*si_unit(:stb))
        yl = "Surface oil rate / 10³ stb/day"
    else
        error("$response not ready.")
    end
    welltypes = []
    ax = Axis(fig[1:4, 1], xlabel = "Time / days", ylabel = yl)
    i = 1
    linehandles = []
    linelabels = []
    for well_name in well_names
        well = wells[well_name]
        label_in_csv = "$well_name:$response"
        ref_pos = findfirst(x -> x == label_in_csv, vec(header))
        qoi = copy(well[response]).*ys
        qoi_ref = data[:, ref_pos].*ys
        tot_rate = copy(well[:rate])
        @. qoi[tot_rate == 0] = NaN
        grat_ref = data[:, findfirst(x -> x == "$well_name:grat", vec(header))]
        orat_ref = data[:, findfirst(x -> x == "$well_name:orat", vec(header))]
        wrat_ref = data[:, findfirst(x -> x == "$well_name:wrat", vec(header))]
        tot_rate_ref = grat_ref + orat_ref + wrat_ref
        @. qoi_ref[tot_rate_ref == 0] = NaN
        crange = (1, max(length(well_names), 2))
        lh = lines!(ax, time_jutul./day, abs.(qoi),
            color = i,
            colorrange = crange,
            label = "$well_name", colormap = cmap
        )
        push!(linehandles, lh)
        push!(linelabels, "$well_name")
        lines!(ax, time_ref./day, abs.(qoi_ref),
            color = i,
            colorrange = crange,
            linestyle = :dash,
            colormap = cmap
        )
        i += 1
    end
    l1 = LineElement(color = :black, linestyle = nothing)
    l2 = LineElement(color = :black, linestyle = :dash)

    Legend(fig[1:3, 2], linehandles, linelabels, nbanks = 3)
    Legend(fig[4, 2], [l1, l2], ["JutulDarcy.jl", "OPM Flow"])
    fig
end
plot_well_comparison (generic function with 2 methods)

Injector bhp

julia
plot_well_comparison(:bhp, inj, "Bottom hole pressure")

Gas injection rates

julia
plot_well_comparison(:grat, inj, "Gas surface injection rate")

Water injection rates

julia
plot_well_comparison(:wrat, inj, "Water surface injection rate")

Producer bhp

julia
plot_well_comparison(:bhp, prod, "Bottom hole pressure")

Oil production rates

julia
plot_well_comparison(:orat, prod, "Oil surface production rate")

Gas production rates

julia
plot_well_comparison(:grat, prod, "Gas surface production rate")

Example on GitHub

If you would like to run this example yourself, it can be downloaded from the JutulDarcy.jl GitHub repository as a script, or as a Jupyter Notebook


This page was generated using Literate.jl.