Consistent discretizations: Average MPFA and nonlinear TPFA
This example demonstrates how to use alternative discretizations for the pressure gradient term in the Darcy equation, i.e. the approximation of the Darcy flux:
It is well-known that for certain combinations of grid geometry and permeability fields, the classical two-point flux approximation scheme can give incorrect results. This is due to the fact that the TPFA scheme is not a formally consistent method when the product of the permeability tensor and the normal vector does not align with the cell-to-cell vectors over a face (lack of K-orthogonality).
In such cases, it is often beneficial to use a consistent discretization. JutulDarcy includes a class of linear and nonlinear schemes that are designed to be accurate even for challenging grids.
For further details on this class of methods, which differ a bit from the classical MPFA-O type method often seen in the literature, see [9], [10] and [11].
Define a mesh and twist the nodes
This makes the mesh non K-orthogonal and will lead to wrong solutions for the default TPFA scheme.
using Jutul
using JutulDarcy
using LinearAlgebra
using GLMakie
sys = SinglePhaseSystem()
nx = nz = 100
pdims = (1.0, 1.0)
g = CartesianMesh((nx, nz), pdims)
g = UnstructuredMesh(g)
D = dim(g)
v = 0.1
for i in eachindex(g.node_points)
x, y = g.node_points[i]
shiftx = v*sin(π*x)*sin(3*(-π/2 + π*y))
shifty = v*sin(π*y)*sin(3*(-π/2 + π*x))
g.node_points[i] += [shiftx, shifty]
end
nc = number_of_cells(g)
domain = reservoir_domain(g, permeability = 0.1*si_unit(:darcy))
fig = Figure()
Jutul.plot_mesh_edges!(Axis(fig[1, 1]), g)
fig
Create a test problem function
We set up a problem for our given domain with left and right boundary boundary conditions that correspond to a linear pressure drop. We can expect the steady-state pressure solution to be linear between the two faces as there is no variation in permeability or significant compressibility. The function will return the pressure solution at the end of the simulation for a given scheme.
function solve_test_problem(scheme)
model, parameters = setup_reservoir_model(domain, sys,
general_ad = true,
kgrad = scheme,
block_backend = false
)
state0 = setup_reservoir_state(model, Pressure = 1e5)
nc = number_of_cells(g)
bcells = Int64[]
bpres = Float64[]
for k in 1:nz
bnd_l = Jutul.cell_index(g, (1, k))
bnd_r = Jutul.cell_index(g, (nx, k))
push!(bcells, bnd_l)
push!(bpres, 1e5)
push!(bcells, bnd_r)
push!(bpres, 2e5)
end
bc = flow_boundary_condition(bcells, domain, bpres)
forces = setup_reservoir_forces(model, bc = bc)
dt = [si_unit(:day)]
_, states = simulate_reservoir(state0, model, dt,
forces = forces, failure_cuts_timestep = false,
tol_cnv = 1e-6,
linear_solver = GenericKrylov(preconditioner = AMGPreconditioner(:smoothed_aggregation), rtol = 1e-6)
)
return states[end][:Pressure]
end
solve_test_problem (generic function with 1 method)
Solve the test problem with three different schemes
TPFA (two-point flux approximation, inconsistent, linear)
Average MPFA (consistent, linear)
NTPFA (nonlinear two-point flux approximation, consistent, nonlinear)
results = Dict()
for m in [:tpfa, :avgmpfa, :ntpfa]
println("Solving $m")
results[m] = solve_test_problem(m);
end
Solving tpfa
Jutul: Simulating 1 day as 1 report steps
╭────────────────┬──────────┬──────────────┬──────────╮
│ Iteration type │ Avg/step │ Avg/ministep │ Total │
│ │ 1 steps │ 1 ministeps │ (wasted) │
├────────────────┼──────────┼──────────────┼──────────┤
│ Newton │ 5.0 │ 5.0 │ 5 (0) │
│ Linearization │ 6.0 │ 6.0 │ 6 (0) │
│ Linear solver │ 50.0 │ 50.0 │ 50 (0) │
│ Precond apply │ 55.0 │ 55.0 │ 55 (0) │
╰────────────────┴──────────┴──────────────┴──────────╯
╭───────────────┬──────────┬────────────┬────────╮
│ Timing type │ Each │ Relative │ Total │
│ │ ms │ Percentage │ s │
├───────────────┼──────────┼────────────┼────────┤
│ Properties │ 0.1962 │ 0.02 % │ 0.0010 │
│ Equations │ 101.0513 │ 13.17 % │ 0.6063 │
│ Assembly │ 54.5765 │ 7.11 % │ 0.3275 │
│ Linear solve │ 226.8364 │ 24.63 % │ 1.1342 │
│ Linear setup │ 38.7969 │ 4.21 % │ 0.1940 │
│ Precond apply │ 4.9502 │ 5.91 % │ 0.2723 │
│ Update │ 20.9130 │ 2.27 % │ 0.1046 │
│ Convergence │ 89.8762 │ 11.71 % │ 0.5393 │
│ Input/Output │ 47.0993 │ 1.02 % │ 0.0471 │
│ Other │ 275.7316 │ 29.94 % │ 1.3787 │
├───────────────┼──────────┼────────────┼────────┤
│ Total │ 920.9510 │ 100.00 % │ 4.6048 │
╰───────────────┴──────────┴────────────┴────────╯
Solving avgmpfa
Jutul: Simulating 1 day as 1 report steps
╭────────────────┬──────────┬──────────────┬───────────╮
│ Iteration type │ Avg/step │ Avg/ministep │ Total │
│ │ 1 steps │ 18 ministeps │ (wasted) │
├────────────────┼──────────┼──────────────┼───────────┤
│ Newton │ 162.0 │ 9.0 │ 162 (150) │
│ Linearization │ 180.0 │ 10.0 │ 180 (160) │
│ Linear solver │ 914.0 │ 50.7778 │ 914 (667) │
│ Precond apply │ 936.0 │ 52.0 │ 936 (683) │
╰────────────────┴──────────┴──────────────┴───────────╯
╭───────────────┬─────────┬────────────┬────────╮
│ Timing type │ Each │ Relative │ Total │
│ │ ms │ Percentage │ s │
├───────────────┼─────────┼────────────┼────────┤
│ Properties │ 0.1931 │ 0.44 % │ 0.0313 │
│ Equations │ 23.8249 │ 60.86 % │ 4.2885 │
│ Assembly │ 0.5675 │ 1.45 % │ 0.1021 │
│ Linear solve │ 1.0203 │ 2.35 % │ 0.1653 │
│ Linear setup │ 8.4286 │ 19.38 % │ 1.3654 │
│ Precond apply │ 0.2410 │ 3.20 % │ 0.2256 │
│ Update │ 0.3539 │ 0.81 % │ 0.0573 │
│ Convergence │ 3.0907 │ 7.89 % │ 0.5563 │
│ Input/Output │ 1.1505 │ 0.29 % │ 0.0207 │
│ Other │ 1.4469 │ 3.33 % │ 0.2344 │
├───────────────┼─────────┼────────────┼────────┤
│ Total │ 43.4999 │ 100.00 % │ 7.0470 │
╰───────────────┴─────────┴────────────┴────────╯
Solving ntpfa
Jutul: Simulating 1 day as 1 report steps
╭────────────────┬──────────┬──────────────┬───────────╮
│ Iteration type │ Avg/step │ Avg/ministep │ Total │
│ │ 1 steps │ 27 ministeps │ (wasted) │
├────────────────┼──────────┼──────────────┼───────────┤
│ Newton │ 256.0 │ 9.48148 │ 256 (240) │
│ Linearization │ 283.0 │ 10.4815 │ 283 (256) │
│ Linear solver │ 962.0 │ 35.6296 │ 962 (703) │
│ Precond apply │ 988.0 │ 36.5926 │ 988 (722) │
╰────────────────┴──────────┴──────────────┴───────────╯
╭───────────────┬─────────┬────────────┬─────────╮
│ Timing type │ Each │ Relative │ Total │
│ │ ms │ Percentage │ s │
├───────────────┼─────────┼────────────┼─────────┤
│ Properties │ 0.1861 │ 0.43 % │ 0.0476 │
│ Equations │ 27.2028 │ 68.98 % │ 7.6984 │
│ Assembly │ 0.4145 │ 1.05 % │ 0.1173 │
│ Linear solve │ 0.6988 │ 1.60 % │ 0.1789 │
│ Linear setup │ 8.2240 │ 18.86 % │ 2.1053 │
│ Precond apply │ 0.2401 │ 2.13 % │ 0.2372 │
│ Update │ 0.2551 │ 0.59 % │ 0.0653 │
│ Convergence │ 1.8373 │ 4.66 % │ 0.5200 │
│ Input/Output │ 0.7716 │ 0.19 % │ 0.0208 │
│ Other │ 0.6649 │ 1.53 % │ 0.1702 │
├───────────────┼─────────┼────────────┼─────────┤
│ Total │ 43.5980 │ 100.00 % │ 11.1611 │
╰───────────────┴─────────┴────────────┴─────────╯
Plot the results
We plot the pressure solution for each of the schemes, as well as the error. Note that the color axis varies between error plots. As the grid is quite skewed, we observe significant errors for the TPFA scheme, with no significant error for the consistent schemes.
x = domain[:cell_centroids][1, :]
get_ref(x) = x*1e5 + 1e5
x_distinct = sort(unique(x))
sol = get_ref.(x_distinct)
fig = Figure(size = (1200, 600))
for (i, (m, s)) in enumerate(results)
ref = get_ref.(x)
err = norm(s .- ref)/norm(ref)
ax = Axis(fig[1, i], title = "$m")
lines!(ax, x_distinct, sol, color = :red)
scatter!(ax, x, s, label = m, markersize = 1, alpha = 0.5, transparency = true)
ax2 = Axis(fig[2, i], title = "Error=$err")
Δ = s - ref
emin, emax = extrema(Δ)
largest = max(abs(emin), abs(emax))
crange = (-largest, largest)
plt = plot_cell_data!(ax2, g, Δ, colorrange = crange, colormap = :seismic)
Colorbar(fig[3, i], plt, vertical = false)
end
fig
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 example took 52.997929514 seconds to complete.
This page was generated using Literate.jl.