Ver código
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def let(s, L, E, T):
return s**L / (s**L + E * (1 - s)**T)Dos conjuntos de curvas kr y el efecto de los parámetros L, E y T
3 de octubre de 2026
La permeabilidad relativa describe la capacidad de una fase para fluir en presencia de otras, respecto de una permeabilidad de referencia. Se expresa como una razón adimensional:
\[ k_{r\alpha}=\frac{k_{\mathrm{efectiva},\alpha}}{k_{\mathrm{referencia}}} \]
En este ejemplo construiremos curvas LET para aceite–agua y gas–aceite. Definiremos sus extremos, calcularemos kr en Python y veremos cómo cambia la forma de las curvas al variar sus parámetros.
Usaremos parámetros ilustrativos, elegidos para practicar. No representan mediciones de laboratorio ni un ajuste para una roca específica. Todas las saturaciones se introducen como fracción, entre 0 y 1.
Lomeland, Ebeltoft y Thomas presentaron la correlación en A New Versatile Relative Permeability Correlation, SCA2005-32. La formulación emplea tres parámetros empíricos de forma por curva, además de las saturaciones límite y el valor de extremo.
Podemos escribir la función normalizada como:
\[ F(s;L,E,T)=\frac{s^L}{s^L+E(1-s)^T},\qquad 0\leq s\leq1 \]
En el ejemplo elegimos L, E y T positivos. Así, F(0) = 0 y F(1) = 1. Al multiplicar F por el valor de extremo, obtenemos la curva kr de la fase correspondiente.
Los parámetros tienen efectos relacionados: L controla principalmente la parte baja, T la parte alta y E desplaza la transición. Son parámetros de ajuste, no propiedades que se midan directamente como la porosidad. Al final veremos su efecto variando uno a la vez.
Necesitamos NumPy, pandas y Matplotlib. Si hace falta instalarlos, ejecuta pip install numpy pandas matplotlib en la terminal. Ejecuta los fragmentos en orden dentro de la misma sesión o cuaderno.
Evaluaremos la función únicamente dentro del intervalo normalizado [0, 1]. Para usar otros datos, revisa primero que las saturaciones pertenezcan al intervalo de cada sistema.
Consideramos un desplazamiento con saturación de agua creciente. No hay gas, de modo que So = 1 − Sw. Distinguimos Sorw, el aceite residual al desplazamiento con agua.
La saturación normalizada es:
\[ S_{wn}=\frac{S_w-S_{wi}}{1-S_{wi}-S_{orw}} \]
Y las curvas se calculan mediante:
\[ k_{rw}=k_{rw}^{\mathrm{fin}}F(S_{wn};L_w,E_w,T_w) \]
\[ k_{row}=k_{row}^{\mathrm{ini}}F(1-S_{wn};L_{ow},E_{ow},T_{ow}) \]
Usamos krow para identificar la permeabilidad relativa del aceite en el sistema aceite–agua. Los superíndices ini y fin indican los extremos de este desplazamiento.
| Parámetro | Valor del ejemplo |
|---|---|
| Agua inicial, Swi | 0.20 |
| Aceite residual al agua, Sorw | 0.25 |
| krw final, a Sw = 1 − Sorw | 0.35 |
| krow inicial, a Sw = Swi | 0.80 |
| Lw, Ew, Tw | 2.0, 3.0, 2.0 |
| Low, Eow, Tow | 2.0, 1.5, 2.0 |
El intervalo de Sw va de 0.20 a 0.75. Su amplitud es 0.55, por lo que el denominador de la normalización es positivo.
Swi, Sorw = 0.20, 0.25
krw_fin, krow_ini = 0.35, 0.80
Lw, Ew, Tw = 2.0, 3.0, 2.0
Low, Eow, Tow = 2.0, 1.5, 2.0
Sw = np.linspace(Swi, 1 - Sorw, 101)
Swn = (Sw - Swi) / (1 - Swi - Sorw)
krw = krw_fin * let(Swn, Lw, Ew, Tw)
krow = krow_ini * let(1 - Swn, Low, Eow, Tow)
agua_aceite = pd.DataFrame({"Sw": Sw, "krw": krw, "krow": krow})
agua_aceite.iloc[[0, 50, 100]].round(4)| Sw | krw | krow | |
|---|---|---|---|
| 0 | 0.200 | 0.0000 | 0.80 |
| 50 | 0.475 | 0.0875 | 0.32 |
| 100 | 0.750 | 0.3500 | 0.00 |
En Sw = 0.20, krw = 0 y krow = 0.80. Al llegar a Sw = 0.75, krw = 0.35 y krow = 0. Estos valores son los extremos que fijamos, no resultados de un ajuste automático.
La curva del agua aumenta y la del aceite disminuye. El cruce de ambas significa igualdad de kr, pero no necesariamente igualdad de movilidad: la movilidad de cada fase depende también de su viscosidad, mediante kr/μ.
Ahora consideramos un desplazamiento con saturación de gas creciente y agua inmóvil fija en Swi = 0.20. En este conjunto So = 1 − Swi − Sg. El aceite residual al gas, Sorg, puede diferir del aceite residual al agua.
Para este ejemplo tomamos saturación crítica de gas Sgc = 0. La normalización es:
\[ S_{gn}=\frac{S_g}{1-S_{wi}-S_{org}} \]
Las curvas correspondientes son:
\[ k_{rg}=k_{rg}^{\mathrm{fin}}F(S_{gn};L_g,E_g,T_g) \]
\[ k_{rog}=k_{rog}^{\mathrm{ini}}F(1-S_{gn};L_{og},E_{og},T_{og}) \]
krog identifica al aceite en el sistema gas–aceite. Esta normalización corresponde al caso sin umbral positivo de movilidad del gas. Si se necesita Sgc > 0, se debe definir cómo comienza el flujo del gas y ajustar los intervalos de las curvas.
| Parámetro | Valor del ejemplo |
|---|---|
| Agua inmóvil, Swi | 0.20 |
| Aceite residual al gas, Sorg | 0.15 |
| krg final, a Sg = 1 − Swi − Sorg | 0.85 |
| krog inicial, a Sg = 0 | 0.80 |
| Lg, Eg, Tg | 1.5, 1.0, 2.0 |
| Log, Eog, Tog | 3.0, 2.0, 1.5 |
Sg varía de 0 a 0.65. En el extremo final, Sg + Swi + Sorg = 1. Conservamos el mismo kr inicial de aceite en ambos conjuntos, correspondiente al estado inicial con Swi = 0.20 y sin gas, usando una misma permeabilidad de referencia.
Sorg = 0.15
krg_fin, krog_ini = 0.85, 0.80
Lg, Eg, Tg = 1.5, 1.0, 2.0
Log, Eog, Tog = 3.0, 2.0, 1.5
Sg = np.linspace(0, 1 - Swi - Sorg, 101)
Sgn = Sg / (1 - Swi - Sorg)
krg = krg_fin * let(Sgn, Lg, Eg, Tg)
krog = krog_ini * let(1 - Sgn, Log, Eog, Tog)
gas_aceite = pd.DataFrame({"Sg": Sg, "krg": krg, "krog": krog})
gas_aceite.iloc[[0, 50, 100]].round(4)| Sg | krg | krog | |
|---|---|---|---|
| 0 | 0.000 | 0.0000 | 0.8000 |
| 50 | 0.325 | 0.4979 | 0.1202 |
| 100 | 0.650 | 0.8500 | 0.0000 |
En Sg = 0 obtenemos krg = 0 y krog = 0.80. En Sg = 0.65, krg = 0.85 y krog = 0. La diferencia entre este gráfico y el anterior proviene tanto de sus saturaciones límite como de los valores de extremo y parámetros de forma.
Estos son dos conjuntos bifásicos. Para describir agua, aceite y gas móviles simultáneamente se necesita un modelo de tres fases. Tampoco incluimos ciclos de histéresis en este ejemplo.
Para separar los efectos, utilizaremos una curva normalizada con extremo igual a 1 y parámetros base L = 2, E = 1 y T = 2. En cada panel cambiaremos un solo parámetro, dejando los otros dos fijos.
s = np.linspace(0, 1, 101)
base = {"L": 2.0, "E": 1.0, "T": 2.0}
variaciones = {"L": [1, 2, 4], "E": [0.5, 1, 3], "T": [1, 2, 4]}
fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True)
for ax, (nombre, valores) in zip(axes, variaciones.items()):
for valor in valores:
parametros = base.copy()
parametros[nombre] = valor
ax.plot(s, let(s, **parametros), label=f"{nombre} = {valor}")
ax.set_xlabel("Saturación normalizada, s")
ax.set_title(f"Variación de {nombre}")
ax.grid(alpha=0.3)
ax.legend()
axes[0].set_ylabel("kr normalizada")
plt.tight_layout()
plt.show()
Para la función creciente F(s), a una saturación interior fija y con los demás parámetros constantes:
Los extremos permanecen en 0 y 1. En las curvas de aceite el argumento es 1 − Swn o 1 − Sgn, por lo que debemos tener en cuenta esa inversión al interpretar el eje de saturación de agua o gas.
Antes de cambiar L, E y T, define el proceso de desplazamiento, las saturaciones límite y los valores de extremo. Verifica también que todas las permeabilidades relativas utilicen una base de referencia coherente.
En estos ejemplos, las curvas quedan entre cero y su extremo y cambian de forma monótona. Las kr no tienen que sumar uno: cada fase tiene su propia relación entre saturación y capacidad de flujo.
Para representar una roca real, los parámetros deben calibrarse con información SCAL y con el proceso de desplazamiento correspondiente. Los valores de este post permiten aprender el cálculo y explorar la forma de las curvas.
Lomeland, F., Ebeltoft, E. y Thomas, W. H. (2005). A New Versatile Relative Permeability Correlation. SCA2005-32. International Symposium of the Society of Core Analysts, Toronto, Canadá, 21–25 de agosto. Consultar el artículo. Ecuaciones bifásicas en los apéndices A y B.