Ecuación de balance de materia: ejemplo 1, parte 1
Ejemplo de cálculo usando la ecuación de balance de materia con un modelo sin capa de gas ni acuífero. Método de Diagnóstico de Havlena-Odeh y Gráfico de Campbell
# Unidades del CSV: Np en MMSTB, Gp en miles de MMscf (10^9 scf),# Rs y Rp en Mscf/STB, Bg en RB/Mscf. M=10^3 y MM=10^6.# Por eso (Rp-Rs)*Bg es RB/STB y F resulta en MMRB.# Rp es Gp/Np acumulados, no el GOR instantáneo.# Las columnas Rp, Rs y Bg se conservan con el redondeo publicado.
📌 Ecuaciones del Balance de Materia
Las siguientes ecuaciones permiten crear los gráficos de diagnóstico de Campbell y Havlena-Odeh. En este ejemplo se supone \(m=0\), sin acuífero, sin inyección y sin producción de agua reportada. En el caso general, \(F\) incluye además \(W_p B_w\). Aquí \(W_e\), cuando se menciona, se expresa en RB (ya a condiciones de yacimiento).
En el caso del gráfico de Campbell, el yacimiento es volumétrico, por lo que:
\[ W_e = 0 \]
En esta variante del gráfico, el eje horizontal es la producción acumulada \(N_p\) y el vertical es \(F/E_t\). Para un modelo volumétrico consistente, \(F/E_t\) se aproxima a una horizontal igual a \(N\). El manual de MBAL usa también \(F\) como eje horizontal. No se trata del término independiente de una regresión de \(N_p\).
📌 Reorganización de la Ecuación de Havlena-Odeh
En los métodos de diagnóstico de Havlena-Odeh, la ecuación (1) se reorganiza de la siguiente forma:
\[
F - W_e = N E_t
\]
Por lo tanto, un gráfico de ( (F - W_e) ) contra ( E_t ) producirá una línea recta que pasa por el origen, cuya pendiente representa el petróleo original en sitio ( N ).
📌 Cálculo de ( F ) y ( E_t ) en Python
Ver código
import numpy as np# Si el archivo fue cargado correctamente# Extraer columnas relevantesNp = df["Np"] # Producción acumulada de petróleo (MMSTB)Bo = df["Bo"] # Factor de volumen de formación del petróleoBg = df["Bg"] # Factor de volumen de formación del gasRp = df["Rp"] # Razón gas-petróleo acumulada (Mscf/STB)Rs = df["Rs"] # Razón gas disueltoPavg = df["Pavg"] # Presión promedio del yacimiento (psia)#Parámetros inicialesSw =0.15cf =0.0000035cw =0.000007Boi = Bo.iloc[0] # Bo inicialRsi = Rs.iloc[0] # Rs inicialPi = Pavg.iloc[0] # Presión inicial# Cálculo de F y EtF = Np * (Bo + (Rp - Rs) * Bg)Et = Boi*(((Bo-Boi)+(Rsi-Rs)*Bg)/Boi+(Pi-Pavg)*(cw*Sw+cf)/(1-Sw))# Agregar los resultados al dataframedf["F"] = Fdf["Et"] = Etdf["F_Et"] = F / Et.where(Et >0) # Excluir 0/0 del estado inicial# Mostrar los resultados calculadosprint(df)
El gráfico de Campbell permite evaluar si \(F/E_t\) se mantiene aproximadamente constante. Con este CSV, los valores están entre 242.6 y 255.3 MMSTB. La referencia de 250 MMSTB ayuda a comparar; no es una estimación única obtenida de una intersección.
Ver código
import matplotlib.pyplot as plt# Gráfico de Np vs F_ETplt.figure(figsize=(8,5))plt.scatter(df["Np"], df["F_Et"], color='blue', label="Datos")# Calcular la regresión lineal solo con datos limpiosdf_clean = df.dropna(subset=["F_Et"])coeficientes = np.polyfit(df_clean["Np"], df_clean["F_Et"], 1) # Ajuste lineal (grado 1)polinomio = np.poly1d(coeficientes) # Crear función polinómica# Evaluar la línea de ajuste en los valores de Np limpiosajuste_y = polinomio(df_clean["Np"])# Graficar la línea de ajusteplt.plot(df_clean["Np"], ajuste_y, 'r--', label=f"Ajuste lineal: y = {coeficientes[0]:.2f}x + {coeficientes[1]:.2f}")# Agregar línea horizontal en y=250 (similar a geom_hline)plt.axhline(y=250, color='black', linestyle='-', linewidth=1)# Agregar texto en (x=2, y=270) (similar a geom_text)plt.text(5, 270, "N = 250 MMSTB", fontsize=12, color="black")plt.xlabel("Np acumulado (MMSTB)")plt.ylabel("F/Et (MMSTB)")plt.ylim(-0, 400)
(0.0, 400.0)
Ver código
plt.title("Gráfico de Campbell ")plt.legend()plt.grid()plt.show()
📊 Gráfico de Havlena-Odeh
El ajuste físico se hace por el origen, porque \(F=NE_t\). Con el CSV publicado y mínimos cuadrados no ponderados se obtiene N ≈ 253.76 MMSTB. Un ajuste con intercepto libre produce otra pendiente y sirve para explorar desviaciones, pero no es el mismo ajuste.
Ver código
# Gráfico de Et vs Fplt.figure(figsize=(8,5))plt.scatter(df["Et"], df["F"], color='blue', label="Datos")# Ajuste por el origen: N = sum(Et*F) / sum(Et^2)mask = (df["Et"] >0) & np.isfinite(df["F"])x = df.loc[mask, "Et"]y = df.loc[mask, "F"]N_ajustado = np.dot(x, y) / np.dot(x, x)x_linea = np.array([0, x.max()])plt.plot(x_linea, N_ajustado*x_linea, 'r--', label=f"Origen fijo: N = {N_ajustado:.2f} MMSTB")plt.xlabel("Et (RB/STB)")plt.ylabel("F (MMRB)")plt.ylim(-0, 70)
Una relación aproximadamente lineal y coherente con el origen es compatible con el modelo volumétrico supuesto.
Las desviaciones pueden sugerir entrada de agua, pero también errores de presión promedio, PVT, producción o un modelo incompleto. Una curva por sí sola no identifica el mecanismo.
La pendiente del ajuste por el origen estima el petróleo original en sitio, N. No representa reservas recuperables.
La presión utilizada es la presión promedio del yacimiento, no la presión de fondo fluyente de un pozo. Las propiedades PVT y las unidades deben corresponder a cada registro de presión.
Referencias
Dake, L.P. Fundamentals of Reservoir Engineering, capítulo 3, ecuaciones 3.8–3.12.
Ahmed, T. (2019). Reservoir Engineering Handbook, 5.ª ed., capítulo 11.
Petroleum Experts. MBAL Help, febrero de 2016, sección 2.16.2.1.6.