Skip to content

Commit

Permalink
Merge pull request #21 from hrashid10/master
Browse files Browse the repository at this point in the history
3D version of pressure management fractured cases
  • Loading branch information
omalled authored Sep 30, 2024
2 parents c4d4c21 + b8ed7dd commit 54484c1
Show file tree
Hide file tree
Showing 2 changed files with 326 additions and 0 deletions.
27 changes: 27 additions & 0 deletions examples/pressure_management_inj/monte_carlo_fracture_3D.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Random
Random.seed!(1)
include("weeds_fracture_3D.jl")
x_test = randn(3 * num_eigenvectors + 2) #the input to the function should follow an N(0, I) distribution -- the eigenvectors and eigenvalues are embedded inside f
@show f(x_test) #compute the pressure at the critical location
@show extrema(grad_f(x_test)) #compute the gradient of the pressure at the critical location with respect to the eigenvector coefficients (then only show the largest/smallest values so it doesn't print a huge array)

#Quick demo of how you could do Monte Carlo to get the distribution of pressures at the critical location -- hopefully the method Georg described can help us understand the tail distribution better
numruns = 10 ^ 3
fs = Float64[]
@time for i = 1:numruns
x_test = randn(3 * num_eigenvectors + 2)
push!(fs, f(x_test))
@show i
end
@show sum(fs) / length(fs)
@show extrema(fs)

fig, axs = PyPlot.subplots(1, 2)
axs[1].hist(fs, bins=30)
axs[1].set(xlabel="Pressure [MPa] at critical location", ylabel="Count (out of $(numruns))")
axs[2].hist(log.(fs), bins=30)
axs[2].set(xlabel="log(Pressure [MPa]) at critical location", ylabel="Count (out of $(numruns))")
#display(fig); println()
#fig.show()
fig.savefig("distribution_of_pressure.png")
PyPlot.close(fig)
299 changes: 299 additions & 0 deletions examples/pressure_management_inj/weeds_fracture_3D.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import DPFEHM
import GaussianRandomFields
import PyPlot
import Zygote

plotstuff = false

module FractureMaker
mutable struct FractureTip
i::Int
j::Int
k::Int
di::Int
dj::Int
dk::Int
alive::Bool
end

struct Material
isfractured::Array{Float64, 3}
end

function Material(n, m,p)
return Material(zeros(n, m,p))
end

inside(m::Material, tip::FractureTip) = inside(m.isfractured, tip)

function inside(A::Array{Float64, 3}, tip::FractureTip)
if tip.di ==0
return tip.i <= size(A, 1) && tip.i > 0 && tip.j <= size(A, 2) && tip.j > 0 && tip.k <= size(A, 3) && tip.k > 0 && (tip.k +1) <= size(A, 2) && (tip.k + 1)> 0 && (tip.k -1) <= size(A, 2) && (tip.k - 1)> 0 && (tip.k +2) <= size(A, 2) && (tip.k +2)> 0 && (tip.k -2) <= size(A, 2) && (tip.k -2)> 0
else
return tip.i <= size(A, 1) && tip.i > 0 && tip.j <= size(A, 2) && tip.j > 0 && tip.k <= size(A, 3) && tip.k > 0 && (tip.k +1) <= size(A, 2) && (tip.k + 1)> 0 && (tip.k -1) <= size(A, 2) && (tip.k - 1)> 0 && (tip.k +2) <= size(A, 2) && (tip.k +2)> 0 && (tip.k -2) <= size(A, 2) && (tip.k -2)> 0
end
end

function timestep!(m::Material, tips::Array{FractureTip})
for tip in tips
if inside(m, tip) && tip.alive
if tip.di ==0
m.isfractured[tip.i, tip.j, tip.k] = true
m.isfractured[tip.i, tip.j, tip.k+1] = true
m.isfractured[tip.i, tip.j, tip.k-1] = true
m.isfractured[tip.i, tip.j, tip.k+2] = true
m.isfractured[tip.i, tip.j, tip.k-2] = true
else
m.isfractured[tip.i, tip.j, tip.k] = true
m.isfractured[tip.i, tip.j, tip.k+1] = true
m.isfractured[tip.i, tip.j, tip.k-1] = true
m.isfractured[tip.i, tip.j, tip.k+2] = true
m.isfractured[tip.i, tip.j, tip.k-2] = true
end
end
if tip.alive
tip.i += tip.di
tip.j += tip.dj
tip.k += tip.dk
end
if inside(m, tip) && m.isfractured[tip.i, tip.j, tip.k] == true
tip.alive = false
end
end
end

function simulate(mat::Material, tips, numtimesteps)
for k = 1:numtimesteps
timestep!(mat, tips)
end
return mat, tips
end
end # module FractureMaker

# Set up the mesh
ns = [101, 101, 50]#mesh size -- make it at least 100x200 to have the resolution for the fractures
steadyhead = 0e0 #meters
width = 1000 #meters
heights = [200, 100, 200]
mins = [0, 0, 0] #meters
maxs = [width, width, sum(heights)] #meters
coords, neighbors, areasoverlengths, volumes = DPFEHM.regulargrid3d(mins, maxs, ns)

masks = [zeros(Bool, prod(ns)) for i = 1:3]
for i = 1:size(coords, 2)
if coords[3, i] > heights[1] + heights[2]
masks[3][i] = true
elseif coords[3, i] > heights[1]
masks[2][i] = true
else
masks[1][i] = true
end
end
masks = map(x->reshape(x, reverse(ns)...), masks)

masksPlot = zeros(prod(ns),1)

for i = 1:size(coords, 2)
if coords[3, i] > heights[1] + heights[2]
masksPlot[i] = 1
elseif coords[3, i] > heights[1]
masksPlot[i] =2
else
masksPlot[i] = 3
end
end
# Create a new figure for the 3D plot
if plotstuff
fig = PyPlot.figure()
ax = fig[:add_subplot](111, projection="3d")
scatter_plot = ax[:scatter](coords[1,:],coords[2,:],coords[3,:], c=masksPlot, cmap="viridis")
display(fig)
println()
PyPlot.close(fig)
end
#setup the fracture network
material = FractureMaker.Material(reverse(ns)...) #Logical array size equal to total number of cell

true_ind = findall( masks[2])
true_ks=[]
for i=1:length(true_ind)
global true_ks=push!(true_ks,true_ind[i][1])
end
true_ks=unique(true_ks)
num_horizontal_fractures = sum(heights) ÷ 30 #put a horizontal fracture every 30 meters or so
tips = Array{FractureMaker.FractureTip}(undef, 2 * num_horizontal_fractures)
for frac = 1:num_horizontal_fractures
i = rand(true_ks)
j = rand(1:ns[2])
k = rand(1:ns[1])
tips[2 * frac - 1] = FractureMaker.FractureTip(i,j,k, 0, 1, 0, true)
tips[2 * frac] = FractureMaker.FractureTip(i,j,k, 0, -1, 0, true)
end
FractureMaker.simulate(material, tips, 9 * ns[1] ÷ 20)#the time steps let the fracture grow to be at most a little less than the width of the domain
horizontal_fracture_mask = copy(material.isfractured) .* masks[2]

num_vertical_fractures = width ÷ 10#put a vertical fracture every 10 meters or so
tips = Array{FractureMaker.FractureTip}(undef, 2 * num_vertical_fractures)
for frac = 1:num_vertical_fractures
i = rand(true_ks)
j = rand(1:ns[2])
k = rand(1:ns[1])
tips[2*frac-1] = FractureMaker.FractureTip(i,j,k,1,0,0, true)
tips[2*frac] = FractureMaker.FractureTip(i, j,k,-1,0,0, true)
end
FractureMaker.simulate(material, tips, ns[3] * heights[2] ÷ (3 * sum(heights)))
vertical_fracture_mask = copy(material.isfractured) .* masks[2] - horizontal_fracture_mask

horizontal_fracture_mask_plot=reshape(horizontal_fracture_mask,prod(ns))
vertical_fracture_mask_plot=reshape(vertical_fracture_mask,prod(ns))

if plotstuff
fig = PyPlot.figure()
ax = fig[:add_subplot](111, projection="3d")
using Colors
category_colors = [
[1, 0, 0, 0.0],
[0, 1, 0, 1.0],
]
colors = [category_colors[c+1] for c in Int.(horizontal_fracture_mask_plot)]
scatter_plot = ax[:scatter](coords[1,:],coords[2,:],coords[3,:], c=colors)
display(fig)
println()
PyPlot.close(fig)


category_colors = [
[0, 0, 0, 0.0],
[1, 0, 0, 1.0],
]
fig = PyPlot.figure()
ax = fig[:add_subplot](111, projection="3d")
colors = [category_colors[c+1] for c in Int.(vertical_fracture_mask_plot)]
scatter_plot = ax[:scatter](coords[1,:],coords[2,:],coords[3,:], c=colors)
display(fig)
println()
PyPlot.close(fig)
end
# Set up the eigenvector parameterization of the geostatistical log-permeability field
num_eigenvectors = 200
sigma = 1.0
lambda = 50
mean_log_conductivities = [log(1e-4), log(1e-8), log(1e-3)] #log(1e-4 [m/s]) -- similar to a pretty porous oil reservoir
cov = GaussianRandomFields.CovarianceFunction(2, GaussianRandomFields.Matern(lambda, 1; σ=sigma))
x_pts = range(mins[1], maxs[1]; length=ns[1])
y_pts = range(mins[2], maxs[2]; length=ns[2])
grf = GaussianRandomFields.GaussianRandomField(cov, GaussianRandomFields.KarhunenLoeve(num_eigenvectors), x_pts, y_pts)
parameterization = copy(grf.data.eigenfunc)
sigmas = copy(grf.data.eigenval)


logKs = zeros(reverse(ns)...)
parameterization3D=zeros(ns[3],ns[1]*ns[2],num_eigenvectors)
sigmas3D=zeros(ns[3],num_eigenvectors)
logKs2d = GaussianRandomFields.sample(grf)'#generate a random realization of the log-conductivity field
for i = 1:ns[3]#copy the 2d field to each of the 3d layers
if i< true_ks[1]
j=1
elseif i>true_ks[end]
j=3
else
j=2
end
t=view(parameterization3D, i, :,:)
t.=parameterization
m=view(sigmas3D, i, :)
m.=sigmas
v = view(logKs, i, :, :)
v .= mean_log_conductivities[j].+logKs2d
end

if plotstuff
#plot the log-conductivity
fig, ax = PyPlot.subplots()
img = ax.imshow(logKs[:, 50, :], origin="lower")
ax.title.set_text("Conductivity Field")
fig.colorbar(img)
display(fig)
println()
PyPlot.close(fig)
end
#play with these four numbers to make the fractures more or less likely to enable lots of flow from the lower reservoir to the upper reservoir
horizontal_fracture_mean_conductivity = log(1e-10)
vertical_fracture_mean_conductivity = log(1e-10)
horizontal_fracture_sigma_conductivity = 10
vertical_fracture_sigma_conductivity = 10
function x2logKs(x)
logKs = [log.(exp.(sum(masks[j][i,:,:]' .* reshape(parameterization3D[i,:,:] * (sigmas3D[i,:] .* x[(j - 1) * num_eigenvectors + 1:j * num_eigenvectors]) .+ mean_log_conductivities[j], ns[1:2]...) for j = 1:3)') +
horizontal_fracture_mask[i,:,:] * exp(horizontal_fracture_mean_conductivity + horizontal_fracture_sigma_conductivity * x[end - 1]) +
vertical_fracture_mask[i,:,:] * exp(vertical_fracture_mean_conductivity + vertical_fracture_sigma_conductivity * x[end])) for i in 1:ns[3]]
logKs=cat(logKs..., dims=3) # concatenate along the first dimension
return logKs = permutedims(logKs, (3, 2, 1))
end
logKs = x2logKs(randn(3 * num_eigenvectors + 2))
if plotstuff
for i=1:101
fig, axs = PyPlot.subplots(1, 1)
im2=axs.imshow(logKs[:, i, :], origin="lower")
display(fig); println()
PyPlot.close(fig)
@show i
end
end
# Make the boundary conditions be dirichlet with a fixed head on the left and right boundaries with zero flux on the top and bottom
dirichletnodes = Int[]
dirichleths = zeros(size(coords, 2))
for i = 1:size(coords, 2)
if coords[1, i] == width || coords[1, i] == 0
push!(dirichletnodes, i)
dirichleths[i] = steadyhead
end
end
# Set up the location of the injection and critical point
function get_nodes_near(coords, obslocs)
obsnodes = Array{Int}(undef, length(obslocs))
for i = 1:length(obslocs)
obsnodes[i] = findmin(map(j->sum((obslocs[i] .- coords[:, j]) .^ 2), 1:size(coords, 2)))[2]
end
return obsnodes
end

injection_node, critical_point_node = get_nodes_near(coords, [[width / 2,width / 2, heights[1] / 2], [width / 2, width / 2, heights[1] + heights[2] + 0.5 * heights[3]]]) #put the critical point near (-80, -80) and the injection node near (80, 80)
injection_nodes = [injection_node]

# Set the Qs for the whole field to zero, then set it to the injection rate divided by the number of injection nodes at the injection nodes
Qs = zeros(size(coords, 2))
Qinj = 0.031688 #injection rate [m^3/s] (1 MMT water/yr)
Qs[injection_nodes] .= Qinj / length(injection_nodes)

logKs2Ks_neighbors(Ks) = exp.(0.5 * (Ks[map(p->p[1], neighbors)] .+ Ks[map(p->p[2], neighbors)]))
function solveforh(logKs)
#@show length(logKs)
@assert length(logKs) == length(Qs)
Ks_neighbors = logKs2Ks_neighbors(logKs)
result = DPFEHM.groundwater_steadystate(Ks_neighbors, neighbors, areasoverlengths, dirichletnodes, dirichleths, Qs; linear_solver=DPFEHM.cholesky_solver)
return result
end

solveforheigs(x) = solveforh(x2logKs(x))
# Set up the functions that compute the pressure at the critical point and the gradient of the pressure at the critical point with respect to the eigenvector coefficients
f(x) = solveforh(x2logKs(x))[critical_point_node]*9.807*997*1e-6 #convert from head (meters) to pressure (MPa) (for water 25 C)
grad_f(x) = Zygote.gradient(f, x)[1]

if plotstuff
import Random
Random.seed!(1)
x_test = randn(3 * num_eigenvectors + 2) #the input to the function should follow an N(0, I) distribution -- the eigenvectors and eigenvalues are embedded inside f
@show x_test[1:3]
@show f(x_test) #compute the pressure at the critical location
#compute the gradient of the pressure at the critical location with respect to the eigenvector coefficients (then only show the largest/smallest values so it doesn't print a huge array)
@show extrema(grad_f(x_test))
P_mat=reshape(solveforh(x2logKs(x_test)), reverse(ns)...)
fig, ax = PyPlot.subplots()
img = ax.imshow(P_mat[:, :,50], origin="lower")
ax.title.set_text("pressure Field")
fig.colorbar(img)
display(fig)
println()
PyPlot.close(fig)
end

0 comments on commit 54484c1

Please sign in to comment.