Flujo Fraccional de Buckley-Leverett y Método de Welge
Ecuación de avance frontal, saturación de breakthrough y pronóstico de producción
Yacimientos
Producción
Python
R
Implementación completa de la ecuación de flujo fraccional de Buckley-Leverett con el método gráfico de Welge: permeabilidades relativas de Corey, curva de fw, saturación del frente, pronóstico de producción y perfil de saturación.
En 1942, Buckley y Leverett desarrollaron un modelo para describir el desplazamiento inmiscible de un fluido por otro en un medio poroso — la base teórica de toda inyección de agua. El modelo asume fluidos incompresibles, sistema homogéneo y flujo unidimensional, ignorando los efectos de presión capilar y gravedad.
La solución nos da el perfil de saturación a lo largo del yacimiento y permite calcular cuándo llega el agua al pozo productor (breakthrough), cuánto aceite se recupera, y cómo se comporta la producción después.
¿Por qué es importante?
Es el fundamento de todo diseño de inyección de agua. Sin entender Buckley-Leverett, no puedes evaluar si un waterflood va a funcionar ni cuánto aceite vas a recuperar.
Video
Ecuación de Flujo Fraccional
El flujo fraccional de agua (\(f_w\)) se define como la fracción del flujo total que es agua:
\[f_w = \frac{q_w}{q_o + q_w}\]
Aplicando la ley de Darcy para flujo simultáneo de aceite y agua en un sistema horizontal (sin gravedad ni presión capilar):
Figure 2: Curvas de permeabilidad relativa de Corey.
Paso 2 — Curva de Flujo Fraccional y Tangente de Welge
El método de Welge permite encontrar gráficamente la saturación del frente de choque (\(S_{wbt}\)): se traza una línea desde el punto \((S_{wi}, f_{wi})\) tangente a la curva de \(f_w\). El punto de tangencia define la saturación de breakthrough, y la extensión a \(f_w = 1\) da la saturación media (\(\bar{S}_w\)).
<string>:2: RuntimeWarning: invalid value encountered in power
Ver código
# Flujo fraccional en SwiSwDi = (Swi - Swc) / (1- Sor - Swc)fwi = SwDi**nw / (SwDi**nw + A * (1- SwDi)**no)# Pendiente desde (Swi, fwi) a cada punto de la curvaslope = np.where(fw > fwi, (fw - fwi) / (Sw - Swi), 0)
<string>:3: RuntimeWarning: invalid value encountered in divide
Ver código
# Punto de breakthrough = máxima pendienteidx_bt = np.argmax(slope)Swbt = Sw[idx_bt]fwbt = fw[idx_bt]slope_max = slope[idx_bt]# Saturación media (extensión de tangente a fw=1)Swbt_mean = Swbt + (1/slope_max) * (1- fwbt)print(f"Saturación de breakthrough (Swbt): {Swbt:.4f}")
Saturación de breakthrough (Swbt): 0.6640
Ver código
print(f"fw en breakthrough: {fwbt:.4f}")
fw en breakthrough: 0.8962
Ver código
print(f"Saturación media (Sw_mean): {Swbt_mean:.4f}")
days =100# Derivada de fw para todo el rango de SwSwD_all = (Sw - Swc) / (1- Sor - Swc)B1_all = nw * SwD_all**(nw -1) * (1- SwD_all)**noB2_all = no * SwD_all**nw * (1- SwD_all)**(no -1)B3_all = SwD_all**nw + A * (1- SwD_all)**nodfw_all = np.where( B3_all >0, (A * B_coef * (B1_all + B2_all)) / B3_all**2,0)# Posición x(Sw)X = dfw_all * Winj * days *5.6146/ (Ar * Poro)# Rama situada detrás del frente de choquemask_front = Sw >= Swbtx_front_branch = X[mask_front]sw_front_branch = Sw[mask_front]# Ordenar desde el inyector (x = 0) hasta el frente (x = xf)order = np.argsort(x_front_branch)x_front_branch = x_front_branch[order]sw_front_branch = sw_front_branch[order]# Posición del frentexf = np.max(x_front_branch)# Construir explícitamente el salto vertical y el tramo a Swix_profile = np.concatenate([x_front_branch, [xf, xf, L]])sw_profile = np.concatenate([sw_front_branch, [Swbt, Swi, Swi]])fig, ax = plt.subplots(figsize=(10, 6))ax.plot(x_profile, sw_profile, '-', lw=2.5, color='#c0392b')ax.axvline(x=xf, color='gray', ls=':', alpha=0.5)ax.set_xlabel('Distancia x (ft)', fontsize=12)ax.set_ylabel('Sw', fontsize=12)ax.set_title(f'Perfil de Saturación a t = {days} días', fontsize=14, fontweight='bold')ax.grid(True, alpha=0.3)ax.set_xlim(0, L)