Ejemplo 5.11 de Ertekin, T., et. al. (2001) Basic Applied Reservoir Simulation.
Para la malla 1D con bloques centradas mostrada en la figura inferior, determinar la distribución de presión durante el primer año de producción. La presión inicial es de 6000 psi. Las propiedades de la roca y del fluido son Δx = 1000 ft, Δy = 1000 ft and Δz = 75 ft, Bo = 1 RB/STB, co = 3.5 x 10^-6 psi-1, kx = 15 md, ϕ = 0.18, μo = 10 cp. Emplea la formulación implícita y un paso de tiempo de Δt = 15 days.
En la siguiente sección definimos los datos de entrada. Importamos las librerias numpy y pandas para la creación de arreglos y dataframe. Para la visualización de los resultados importamos matplotlib.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
# Definición de parámetros iniciales
= 5 # numero de celdas
nx = 0.001127 # factor de conversión
beta = 5.614 # factor de conversión
alpha
# Dimensiones
= 1000 # ft, dimension in x direction
dx = 1000 # ft, dimension in y direction
dy = 75 # ft, dimension in z direction
dz
# Propiedades del yacimiento
= 0.18 # porosidad fracción
poro = 0 # compresibilidad de la roca psi^-1
Cr = 0.0000035 # compresibilidad del fluido psi^-1
Cf = 15 # permeabilidad, md
k = 1 # factor de volumen de formación del aceite, RB/STB
Bo = 10 # viscosidad, cp
vis
# Información del pozo
= 3.5 # in, radio de pozo
rw = 0 # skin
s = 4 # celda de pozo
cellP = -150 # gasto de producción del pozo, STB/D
qo
# Tiempo
= 15 # paso de tiempo, días
dt = 360 # tiempo total de simulación
TT = 6000 # psi, Icondición inicial Po
Definimos los términos de transmisibilidad en la dirección x
\[T_{x_{i\pm1/2}} = \beta_c\frac{k_x A_x}{\mu B \Delta x}\]
Definimos el termino de acumulación
\[\frac{V_b \phi (c_o + c_{\phi})}{\alpha B_o \Delta t}\]
# Cálculos adicionales
= dy * dz # Faces area
Ax = dx * dy * dz # Cells volume
vol = ((beta * Ax * k) / (vis * Bo * dx)) # East transmisibility
TE = ((beta * Ax * k) / (vis * Bo * dx)) # West transmisibility
TW
= ((vol * poro * (Cf + Cr)) / (alpha * Bo * dt))
Acum = dt
time = np.full(nx, Po) # Presión al tiempo n
Pt = np.zeros(nx) # Presión al tiempo n + 1
Ptdt
# Results dataframes initialization
# For simplicity in Python, we'll use pandas DataFrame
= pd.DataFrame(data={'Time': [0], **{i+1: Pt[i] for i in range(nx)}})
results_cells = np.linspace(dx/2, (dx*nx)-(dx/2), num=nx)
cells_x = pd.DataFrame(columns=['Time', 'Pwf']) results_pwf
Thomas’ algorithm
Este algoritmo es aplicable a problemas de flujo en una dimensión
def thomas(a,b,c,d,n):
= [0] * n
cp = [0] * n
dp
0] = c[0]/b[0]
cp[0] = d[0]/b[0]
dp[
#Forward elimination
for j in range(1,n):
= b[j]-cp[j-1]*a[j]
m = c[j]/m
cp[j] = (d[j]-dp[j-1]*a[j])/m
dp[j]
= [0] * n
x -1] = dp[n-1]
x[n
for j in reversed(range(0,n-1)):
= dp[j]-cp[j]*x[j+1]
x[j]
return(x)
Definimos un ciclo de tiempo usando while(). Como se definió anteriormente, el vector Pt representan los valores de presión al tiempo n, valores conocidos, y el vector Ptdt, los valores de presión al tiempo n + 1, que son los valores incógnita del problema. En este caso la función thomas() nos estará regresando como resultados los valores del vector Ptdt. En cada iteración reasignamos los valores de ptdt a pt para calcular al siguiente paso de tiempo.
# Variables para el control del ciclo
= 0
current_time = 0
steps
# Preparar vectores para el sistema tridiagonal
= np.zeros(nx)
a = np.zeros(nx)
b = np.zeros(nx)
c = np.zeros(nx)
d
while current_time < TT:
# Actualizar vectores para el sistema tridiagonal basado en condiciones de simulación
for i in range(nx):
= TW
a[i] = -(Acum + TE + TW)
b[i] = TE
c[i] = -Acum * Pt[i]
d[i]
# Condiciones de frontera
0] = -(Acum + TE)
b[
-1] = -(Acum + TW)
b[nx
-1] = d[cellP-1] - qo
d[cellP
# Resolver el sistema para encontrar presiones en el siguiente paso de tiempo
= thomas(a, b, c, d,nx)
x
# Actualizar presiones y tiempo
#Pt = Ptdt
for j in range(nx):
= x[j]
Ptdt[j] = Ptdt[j]
Pt[j]
+= dt
current_time += 1
steps
# Almacenar resultados
= results_cells.append(pd.Series([current_time] + list(Ptdt), index=results_cells.columns), ignore_index=True)
results_cells
"Simulación completada con {} pasos.".format(steps)
'Simulación completada con 24 pasos.'
print(results_cells)
Time 1 2 3 4 5
0 0.0 6000.000000 6000.000000 6000.000000 6000.000000 6000.000000
1 15.0 5999.083033 5995.025001 5968.950142 5805.464338 5964.144153
2 30.0 5996.195345 5983.783360 5921.732208 5654.923245 5907.032509
3 45.0 5990.504508 5966.184242 5867.445950 5531.704558 5837.827409
4 60.0 5981.559802 5944.207760 5810.414301 5426.201198 5761.283606
5 75.0 5969.416982 5918.156413 5752.525214 5332.539726 5682.028332
6 90.0 5954.230874 5888.870205 5694.595817 5246.279591 5601.690179
7 105.0 5936.013228 5856.412885 5637.023968 5165.487994 5520.728591
8 120.0 5915.044673 5822.307008 5580.461228 5088.403482 5440.450275
9 135.0 5891.301004 5786.421250 5524.087967 5014.316343 5361.540104
10 150.0 5864.800198 5748.853145 5468.512928 4942.615033 5283.885362
11 165.0 5835.517418 5709.467939 5412.895074 4872.455717 5207.330518
12 180.0 5804.427827 5669.130875 5357.393367 4803.994559 5132.720040
13 195.0 5771.548769 5627.936059 5302.595144 4736.485890 5059.100805
14 210.0 5736.728776 5585.061695 5247.796920 4670.656770 4987.422506
15 225.0 5700.232032 5541.941123 5193.093436 4605.682602 4916.717472
16 240.0 5662.652104 5497.369235 5138.998697 4541.645746 4847.000884
17 255.0 5623.374651 5452.438421 5084.295213 4478.481821 4779.076561
18 270.0 5583.148138 5406.783980 5030.316621 4416.273816 4712.144112
19 285.0 5541.951156 5360.289765 4976.338028 4354.905585 4646.182132
20 300.0 5499.073995 5313.530732 4922.338028 4293.664618 4581.059293
21 315.0 5456.066772 5266.066050 4868.359436 4233.266855 4516.907555
22 330.0 5412.222573 5218.485853 4814.475583 4173.593351 4452.889308
23 345.0 5367.404476 5170.047272 4760.475583 4114.031931 4389.707405
24 360.0 5321.750035 5121.496607 4706.591729 4055.328896 4327.499400
Por último, graficamos la presión de la celda 4, donde esta ubicado el pozo, contra tiempo, y generamos los perfiles de presión a lo largo del yacimiento para cada paso de tiempo.
#Suponiendo que 'results_cells' es tu DataFrame y 'cellP' es el número de celda del pozo
=(10, 6));
plt.figure(figsize'Time'], results_cells[cellP], label=f'Presión en Celda {cellP}');
plt.plot(results_cells['Evolución de la Presión en el Tiempo');
plt.title('Tiempo (días)');
plt.xlabel('Presión (psi)');
plt.ylabel(;
plt.legend()True);
plt.grid(;
plt.show()
#Grafio perfiles de presión
=(10, 6));
plt.figure(figsize
# Iterar sobre cada fila de la tabla para graficar
for index, row in results_cells.iterrows():
1:], label=f"Time: {row['Time']}");
plt.plot(row[
# Configuración del gráfico
[<matplotlib.lines.Line2D object at 0x0000000063282898>]
[<matplotlib.lines.Line2D object at 0x0000000063282BA8>]
[<matplotlib.lines.Line2D object at 0x0000000063282E48>]
[<matplotlib.lines.Line2D object at 0x000000006323AE48>]
[<matplotlib.lines.Line2D object at 0x00000000632923C8>]
[<matplotlib.lines.Line2D object at 0x0000000063292668>]
[<matplotlib.lines.Line2D object at 0x0000000063292908>]
[<matplotlib.lines.Line2D object at 0x0000000063292BA8>]
[<matplotlib.lines.Line2D object at 0x0000000063292E48>]
[<matplotlib.lines.Line2D object at 0x0000000063282E80>]
[<matplotlib.lines.Line2D object at 0x00000000632828D0>]
[<matplotlib.lines.Line2D object at 0x00000000632A1630>]
[<matplotlib.lines.Line2D object at 0x00000000632A18D0>]
[<matplotlib.lines.Line2D object at 0x00000000632A1B70>]
[<matplotlib.lines.Line2D object at 0x00000000632A1E10>]
[<matplotlib.lines.Line2D object at 0x0000000063292E80>]
[<matplotlib.lines.Line2D object at 0x00000000632AF390>]
[<matplotlib.lines.Line2D object at 0x00000000632AF630>]
[<matplotlib.lines.Line2D object at 0x00000000632AF8D0>]
[<matplotlib.lines.Line2D object at 0x00000000632AFB70>]
[<matplotlib.lines.Line2D object at 0x00000000632AFE10>]
[<matplotlib.lines.Line2D object at 0x00000000632A1E48>]
[<matplotlib.lines.Line2D object at 0x00000000632BF390>]
[<matplotlib.lines.Line2D object at 0x00000000632BF630>]
[<matplotlib.lines.Line2D object at 0x00000000632BF8D0>]
'Celda');
plt.xlabel('Presión');
plt.ylabel('Perfil de presión');
plt.title(=(1.12, 1),loc='upper right',prop={'size': 5.8});
plt.legend(bbox_to_anchorTrue);
plt.grid(
# Mostrar el gráfico
; plt.show()
import pyvista as pv
= nx, 1, 1
ni, nj, nk = dx, dy, dz
si, sj, sk
= np.arange(0, (ni + 1) * si, si)
xcorn = np.repeat(xcorn, 2)
xcorn = xcorn[1:-1]
xcorn = np.tile(xcorn, 4 * nj * nk)
xcorn
= np.arange(0, (nj + 1) * sj, sj)
ycorn = np.repeat(ycorn, 2)
ycorn = ycorn[1:-1]
ycorn = np.tile(ycorn, (2 * ni, 2 * nk))
ycorn = np.transpose(ycorn)
ycorn = ycorn.flatten()
ycorn
= np.arange(0, (nk + 1) * sk, sk)
zcorn = np.repeat(zcorn, 2)
zcorn = zcorn[1:-1]
zcorn = np.repeat(zcorn, (4 * ni * nj))
zcorn
= np.stack((xcorn, ycorn, zcorn))
corners = corners.transpose()
corners
= np.asarray((ni, nj, nk)) + 1
dims = pv.ExplicitStructuredGrid(dims, corners)
grid = grid.compute_connectivity()
grid
"colors"] = [5000,4000,3500,3500,2000]
grid.cell_data[
=True) grid.plot(show_edges