*Enrique Meza V.* // kevin.meza.v@uni.pe
Hola, somos un grupo de estudiantes miembros del Capítulo Estudiantil de la *Society of Petrophysicists and Well Log Analysts* en la Universidad Nacional de Ingeniería; organizamos este curso de introducción a la programación en Python en colaboración con el grupo de investigación TRM de acceso abierto y gratuito al público, con el objetivo de mostrar su aplicación en la industria de Oil & Gas.
Antes de empezar tengo que advertirte que ningún lenguaje de programación, por simple que sea, puede aprenderse en profundidad en tan poco tiempo, a no ser que se requiera de experiencia previa en otros lenguajes. Dominar la programación precisa de experiencia, lo cual a su vez requiere de un tiempo mínimo que permita afianzar las estructuras mentales necesarias para entender la secuencia lógica a seguir para desarrollar un programa o proyecto de software.
Python es un lenguaje de programación de alto nivel, presenta un código simple, por lo que es fácil de aprender, ya que requiere una sintaxis única que se centra en la legibilidad; esto explica la creciente popularidad que ha tenido en los ultimos tiempos.
A pesar de su simpleza, es muy utilizado tanto en la industria para servidores y servicios web, así como también en el área academica para redes neuronales, deep learning, simulación, etc.
*Empezamos!!!*¶
Este workshop está orientado a introducir los aspectos básicos de Python y el manejo de las librerías más utilizadas en el ámbito de la investigación para el análisis de datos como son Numpy, Matplotlib, Pandas y Scipy.
Veamos los elementos fundamentales del lenguage de Python con sus variables, como la asignación de valores, tipos de variables (simples y compuestas), operadoraciones aritméticas y estructuras de control (condicionales, y bucles).
# mi primer programa
print('!Bienvenidos al workshop, "Python Aplicado a la Industria del O&G"!')
!Bienvenidos al workshop, "Python Aplicado a la Industria del O&G"!
# número entero (int)
x = 20
# número flotante (float)
y = 0.35
# número complejo (complex)
z = 3 + 4j
# tipo booleano (bool)
r = 1 < 3
# caracteres o texto (str)
t = 'spwla uni student chapter'
# objeto nulo (special)
n = None
print('x es una variable de tipo', type(x))
print('y es una variable de tipo', type(y))
print('z es una variable de tipo', type(z))
print('r es una variable de tipo', type(r))
print('t es una variable de tipo', type(t))
print('n es una variable de tipo', type(n))
x es una variable de tipo <class 'int'> y es una variable de tipo <class 'float'> z es una variable de tipo <class 'complex'> r es una variable de tipo <class 'bool'> t es una variable de tipo <class 'str'> n es una variable de tipo <class 'NoneType'>
# operaciones aritméticas
print('La suma es: ', x + y)
print('La diferencia es: ', z - y)
print('La multiplicación es: ', x * y)
print('La división es: ', z / x)
La suma es: 20.35 La diferencia es: (2.65+4j) La multiplicación es: 7.0 La división es: (0.15+0.2j)
# listas (list)
ls = [1, 2, 3]
# tuplas (tuple)
tp = (1, 2, 3)
# diccionarios (dict)
dc = {'a':1, 'b':2, 'c':3}
# conjuntos (set)
st = {1, 2, 3}
print('ls es una variable de tipo', type(ls))
print('tp es una variable de tipo', type(tp))
print('dc es una variable de tipo', type(dc))
print('st es una variable de tipo', type(st))
ls es una variable de tipo <class 'list'> tp es una variable de tipo <class 'tuple'> dc es una variable de tipo <class 'dict'> st es una variable de tipo <class 'set'>
# lista vacía
list1= []
# lista de enteros
list2 = [1, 2, 3, 4, 5]
# lista con varios tipos de datos
list3 = [81, 'SPWLA', 3.14, True]
# lista con varios tipos de datos
my_list = ['SPWLA', 12, [18, 'Tecnologías de Recobro Mejorado', False], 2.71828]
# indexación en listas
print(my_list[0])
print(my_list[-1])
print(my_list[2][1])
SPWLA 2.71828 Tecnologías de Recobro Mejorado
2# asignar a una variable un valor
a = input('Ingrese el primero número:')
b = input('Ingrese el segundo número:')
# condicional
if a == b:
print('Los números', a, 'y', b, 'son iguales')
elif a < b:
print('El número', a, 'es menor que', b)
else:
print('El número', b, 'es menor que', a)
Ingrese el primero número:15 Ingrese el segundo número:20 El número 15 es menor que 20
# crear una lista
uni = ['UNP', 'UIS', 'UNALM', 'UDO', 'UFRJ']
country = ['Perú', 'Colombia', 'México', 'Venezuela', 'Brasil']
# usando un bucle for
for i in range(len(uni)):
print(uni[i], country[i], sep = ' -> ')
UNP -> Perú UIS -> Colombia UNALM -> México UDO -> Venezuela UFRJ -> Brasil
# usando la función 'enumerate()'
for i, valor in enumerate(uni):
print(i, uni[i], country[i], sep=' -> ')
0 -> UNP -> Perú 1 -> UIS -> Colombia 2 -> UNALM -> México 3 -> UDO -> Venezuela 4 -> UFRJ -> Brasil
# usando un bucle while
i = 0
while i < len(uni):
print(i, uni[i], country[i], sep = ' -> ')
i += 1
0 -> UNP -> Perú 1 -> UIS -> Colombia 2 -> UNALM -> México 3 -> UDO -> Venezuela 4 -> UFRJ -> Brasil
Una librería o biblioteca es un conjunto de funciones implementadas por otro programador que nos facilitan realizar tareas, principalmente porque no debemos volver a programar este código. Será vital el uso de librerías para poder analizar los archivos con información.
Las bibliotecas más importantes para el análisis de datos: *Numpy, Pandas, Matplotlib*.
# importar librerías, estas ya han sido preinstaldas
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# creación de matrices
np.array([1, 2, 3, 4, 5])
array([1, 2, 3, 4, 5])
# creación de una matriz con tipo de variable float
np.array([1, 2, 3, 4, 5], dtype='float32')
array([1., 2., 3., 4., 5.], dtype=float32)
# genera una matriz 1-d de 1 a 36, con un incremento de 3
np.arange(1, 36, 3)
array([ 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34])
# crear 9 puntos de igual espaciado en el rango de 0 a 100
np.linspace(0, 100, 12)
array([ 0. , 9.09090909, 18.18181818, 27.27272727, 36.36363636, 45.45454545, 54.54545455, 63.63636364, 72.72727273, 81.81818182, 90.90909091, 100. ])
# crea una matriz de 34 valores aleatorios en el rango 0-100
np.random.randint (1, 100, 34)
array([ 8, 97, 35, 16, 85, 82, 73, 28, 43, 18, 50, 67, 22, 12, 3, 30, 6, 91, 16, 84, 48, 3, 8, 74, 85, 37, 6, 29, 91, 76, 11, 65, 25, 16])
# operaciones básicas de ndarray
array_A = np.array([[1, 2, 5], [7, 8, 2], [5, 7, 9]])
array_B = np.array([[5, 3, 1], [6, 7, 9], [2, 1, 2]])
# suma de matrices
print(array_A - array_B)
print()
print(array_A + array_B)
print()
print(np.add(array_A, array_B))
[[-4 -1 4] [ 1 1 -7] [ 3 6 7]] [[ 6 5 6] [13 15 11] [ 7 8 11]] [[ 6 5 6] [13 15 11] [ 7 8 11]]
# producto de elementos
print(array_A * array_B)
print()
print(array_A @ array_B)
[[ 5 6 5] [42 56 18] [10 7 18]] [[27 22 29] [87 79 83] [85 73 86]]
Matplotlib (Mat-h Plot Lib-rary) es la libreria estandar de Python para realizar gráficos de diversos tipos a partir de datos contenidos en listas o arrays en el lenguage de programación Python y su extensión matemática NumPy, es muy flexible y tiene muchos valores predeterminados que te ayudarán muchísimo en tú trabajo.
# 100 números linealmente espacios
x = np.linspace(-np.pi, np.pi, 100)
# función seno, y = sen(X)
y = np.sin(x)
# rápida visualización
# plt.plot(x, y)
Ahora grafiquemos dos funciones más, y=2sen(x) y y=3sen(x). Esta vez modifiquemos algunos parámetros.
# tamaño del gráfico
plt.figure(figsize = (8, 5))
# ploteo de las tres funciones
plt.plot(x, y, 'red', label='y = sin(x)')
plt.plot(x, 2*y, 'green', label='y = 2sin(x)')
plt.plot(x, 3*y, 'blue', label='y = 3sin(x)')
# insertar título del gráfico
plt.title('Funciones trigonométricas', size=20, pad=10)
# insertar etiqueta de los ejes
plt.xlabel('X')
plt.ylabel('Y')
# insertar legenda y posición
plt.legend(loc='upper left')
# limitar el eje x
plt.xlim(-4, 4)
plt.grid()
# mostrar gráfico
plt.show()
Pandas (Pa-nel Da -ta) es una herramienta de manipulación y análisis de datos de código abierto rápida, potente, flexible y fácil de usar, construida sobre el lenguaje de programación Python. Asi mismo, un dataframe es una estructura de datos bidimensional, es decir, los datos se alinean de forma tabular en filas y columnas.
# convertir los resultados trigonométricos en un dataframe ('hoja de cálculo')
fun_trig = pd.DataFrame({'X': x, 'Sin(x)': y, '2 Sin(x)': 2*y, '3 Sin(x)': 3*y})
# visualización del dataframe
fun_trig
X | Sin(x) | 2 Sin(x) | 3 Sin(x) | |
---|---|---|---|---|
0 | -3.141593 | -1.224647e-16 | -2.449294e-16 | -3.673940e-16 |
1 | -3.078126 | -6.342392e-02 | -1.268478e-01 | -1.902718e-01 |
2 | -3.014660 | -1.265925e-01 | -2.531849e-01 | -3.797774e-01 |
3 | -2.951193 | -1.892512e-01 | -3.785025e-01 | -5.677537e-01 |
4 | -2.887727 | -2.511480e-01 | -5.022960e-01 | -7.534440e-01 |
... | ... | ... | ... | ... |
95 | 2.887727 | 2.511480e-01 | 5.022960e-01 | 7.534440e-01 |
96 | 2.951193 | 1.892512e-01 | 3.785025e-01 | 5.677537e-01 |
97 | 3.014660 | 1.265925e-01 | 2.531849e-01 | 3.797774e-01 |
98 | 3.078126 | 6.342392e-02 | 1.268478e-01 | 1.902718e-01 |
99 | 3.141593 | 1.224647e-16 | 2.449294e-16 | 3.673940e-16 |
100 rows × 4 columns
# mostrar primeras/últimas 5 filas del dataframe
fun_trig.head()
# fun_trig.tail()
X | Sin(x) | 2 Sin(x) | 3 Sin(x) | |
---|---|---|---|---|
0 | -3.141593 | -1.224647e-16 | -2.449294e-16 | -3.673940e-16 |
1 | -3.078126 | -6.342392e-02 | -1.268478e-01 | -1.902718e-01 |
2 | -3.014660 | -1.265925e-01 | -2.531849e-01 | -3.797774e-01 |
3 | -2.951193 | -1.892512e-01 | -3.785025e-01 | -5.677537e-01 |
4 | -2.887727 | -2.511480e-01 | -5.022960e-01 | -7.534440e-01 |
# mostrar el nombre del índice y las columnas
# fun_trig.index
fun_trig.columns
Index(['X', 'Sin(x)', '2 Sin(x)', '3 Sin(x)'], dtype='object')
# descripción de estadística básica
fun_trig.describe()
X | Sin(x) | 2 Sin(x) | 3 Sin(x) | |
---|---|---|---|---|
count | 1.000000e+02 | 1.000000e+02 | 1.000000e+02 | 1.000000e+02 |
mean | 2.486900e-16 | -2.444926e-17 | -4.889852e-17 | 5.252375e-17 |
std | 1.841258e+00 | 7.071068e-01 | 1.414214e+00 | 2.121320e+00 |
min | -3.141593e+00 | -9.998741e-01 | -1.999748e+00 | -2.999622e+00 |
25% | -1.570796e+00 | -6.957328e-01 | -1.391466e+00 | -2.087198e+00 |
50% | 2.220446e-16 | 0.000000e+00 | 0.000000e+00 | 0.000000e+00 |
75% | 1.570796e+00 | 6.957328e-01 | 1.391466e+00 | 2.087198e+00 |
max | 3.141593e+00 | 9.998741e-01 | 1.999748e+00 | 2.999622e+00 |
# operaciones en el dataframe
print('La desviacíon típica de Sen(x) es: ', fun_trig['Sin(x)'].std())
print('La varianza del Sen(x) es: ', fun_trig['Sin(x)'].var())
print('El percentil 90 del Sen(x) es: ', fun_trig['Sin(x)'].quantile(0.9))
La desviacíon típica de Sen(x) es: 0.7071067811865475 La varianza del Sen(x) es: 0.49999999999999994 El percentil 90 del Sen(x) es: 0.9459909609876092
Acceder al conjunto de datos gratuito de la Universidad de Kansas, estos archivos ZIP contienen todos los archivos LAS disponibles en Kansas Geological Survey (KGS). Descargue el archivo comprimido de 2020.zip
y extraiga el archivo 1051704679.LAS
.
# obtener el conjunto de datos del repositorio abierto (KGS)
!wget 'http://www.kgs.ku.edu/PRS/Scans/Log_Summary/2020.zip'
--2021-03-24 18:24:52-- http://www.kgs.ku.edu/PRS/Scans/Log_Summary/2020.zip Resolving www.kgs.ku.edu (www.kgs.ku.edu)... 129.237.140.42 Connecting to www.kgs.ku.edu (www.kgs.ku.edu)|129.237.140.42|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 308227585 (294M) [application/zip] Saving to: ‘2020.zip’ 2020.zip 100%[===================>] 293.95M 74.8MB/s in 6.9s 2021-03-24 18:24:59 (42.9 MB/s) - ‘2020.zip’ saved [308227585/308227585]
# descomprima el archivo y guárdelo en el directorio 'KGS'
!unzip '/content/2020.zip' -d '/content/KGS_Data'
# descomprima el archuivo las y guárdelo en el directorio 'logs'
!unzip '/content/KGS_Data/1051704679.zip' -d '/content/KGS_Data/log_1051704679'
# instalar la biblioteca lasio para leer el registro de pozo
!pip install lasio
Collecting lasio Downloading https://files.pythonhosted.org/packages/5e/8e/ce58a22ec8454a12f92333a50f2add5f6131218c4815952d6ca7cbd578f0/lasio-0.28-py3-none-any.whl Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from lasio) (1.19.5) Installing collected packages: lasio Successfully installed lasio-0.28
# importar la bibliteca
import lasio
# lea el archivo LAS
path = '/content/KGS_Data/log_1051704679/1051704679.las'
well = lasio.read(path)
# información de registro en la parte del encabezado del archivo LAS
# print(well.keys())
well.curves
[CurveItem(mnemonic="DEPT", unit="F", value="00 000 000 000", descr="Depth", original_mnemonic="DEPT", data.shape=(8791,)), CurveItem(mnemonic="TENS", unit="lbs", value="00 000 000 000", descr="Tension", original_mnemonic="TENS", data.shape=(8791,)), CurveItem(mnemonic="SP", unit="mV", value="00 000 000 000", descr="SP", original_mnemonic="SP", data.shape=(8791,)), CurveItem(mnemonic="RXRT", unit="NONE", value="00 000 000 000", descr="RXRT", original_mnemonic="RXRT", data.shape=(8791,)), CurveItem(mnemonic="RXO", unit="ohmm", value="00 000 000 000", descr="RXO", original_mnemonic="RXO", data.shape=(8791,)), CurveItem(mnemonic="RT90", unit="ohmm", value="00 000 000 000", descr="90in Resistivity 2ft Res", original_mnemonic="RT90", data.shape=(8791,)), CurveItem(mnemonic="RT60", unit="ohmm", value="00 000 000 000", descr="60in Resistivity 2ft Res", original_mnemonic="RT60", data.shape=(8791,)), CurveItem(mnemonic="RT30", unit="ohmm", value="00 000 000 000", descr="30in Resistivity 2ft Res", original_mnemonic="RT30", data.shape=(8791,)), CurveItem(mnemonic="RT20", unit="ohmm", value="00 000 000 000", descr="20in Resistivity 2ft Res", original_mnemonic="RT20", data.shape=(8791,)), CurveItem(mnemonic="RT10", unit="ohmm", value="00 000 000 000", descr="10in Resistivity 2ft Res", original_mnemonic="RT10", data.shape=(8791,)), CurveItem(mnemonic="RT", unit="ohmm", value="00 000 000 000", descr="RT", original_mnemonic="RT", data.shape=(8791,)), CurveItem(mnemonic="RMUD", unit="ohmm", value="00 000 000 000", descr="RMUD", original_mnemonic="RMUD", data.shape=(8791,)), CurveItem(mnemonic="RHOB", unit="g/cc", value="00 000 000 000", descr="Density", original_mnemonic="RHOB", data.shape=(8791,)), CurveItem(mnemonic="QN", unit="NONE", value="00 000 000 000", descr="NearQuality", original_mnemonic="QN", data.shape=(8791,)), CurveItem(mnemonic="QF", unit="NONE", value="00 000 000 000", descr="FarQuality", original_mnemonic="QF", data.shape=(8791,)), CurveItem(mnemonic="PE", unit="", value="00 000 000 000", descr="Pe", original_mnemonic="PE", data.shape=(8791,)), CurveItem(mnemonic="NPHS", unit="decp", value="00 000 000 000", descr="Neutron Porosity Sand", original_mnemonic="NPHS", data.shape=(8791,)), CurveItem(mnemonic="NPHL", unit="decp", value="00 000 000 000", descr="Neu Por Lime", original_mnemonic="NPHL", data.shape=(8791,)), CurveItem(mnemonic="NPHI", unit="decp", value="00 000 000 000", descr="Neutron Porosity", original_mnemonic="NPHI", data.shape=(8791,)), CurveItem(mnemonic="NPHD", unit="decp", value="00 000 000 000", descr="Neutron Porosity Dolo", original_mnemonic="NPHD", data.shape=(8791,)), CurveItem(mnemonic="GR", unit="api", value="00 000 000 000", descr="Gamma API", original_mnemonic="GR", data.shape=(8791,)), CurveItem(mnemonic="DRHO", unit="g/cc", value="00 000 000 000", descr="DensityCorr", original_mnemonic="DRHO", data.shape=(8791,)), CurveItem(mnemonic="DPHS", unit="decp", value="00 000 000 000", descr="DenPhiSand", original_mnemonic="DPHS", data.shape=(8791,)), CurveItem(mnemonic="DPHI", unit="decp", value="00 000 000 000", descr="DensityPorosity", original_mnemonic="DPHI", data.shape=(8791,)), CurveItem(mnemonic="DPHD", unit="decp", value="00 000 000 000", descr="DenPhiDolo", original_mnemonic="DPHD", data.shape=(8791,)), CurveItem(mnemonic="DLIM", unit="decp", value="00 000 000 000", descr="DenPhiLime", original_mnemonic="DLIM", data.shape=(8791,)), CurveItem(mnemonic="CT90", unit="mmo/m", value="00 000 000 000", descr="90in Conductivity 2ft Res", original_mnemonic="CT90", data.shape=(8791,)), CurveItem(mnemonic="CALI", unit="in", value="00 000 000 000", descr="Caliper", original_mnemonic="CALI", data.shape=(8791,)), CurveItem(mnemonic="MINV", unit="ohmm", value="00 000 000 000", descr="MicrologLateral", original_mnemonic="MINV", data.shape=(8791,)), CurveItem(mnemonic="MNOR", unit="ohmm", value="00 000 000 000", descr="MicrologNormal", original_mnemonic="MNOR", data.shape=(8791,))]
# tamaño del gráfico
plt.figure(figsize = (15, 4))
# traza de los datos del registro de GR
plt.plot(well['DEPT'], well['GR'], color = 'black')
plt.title('Gamma Ray', size = 18)
plt.xlabel('GR (api)'); plt.ylabel('Depth (m)')
plt.show()
# tamaño y título del gráfico
plt.figure(figsize=(12,10))
plt.suptitle('Well Logs: RUMBACK B #21-2', size=20, y =1.03)
# traza de los registros: SP-GR-RT-RHOB-NPHI
plt.subplot(1, 5, 1)
plt.plot(well['SP'], well['DEPT'], color='green')
plt.ylim(max(well['DEPT']), min(well['DEPT']))
plt.title('Self Potencial (SP)')
plt.grid()
plt.subplot(1, 5, 2)
plt.plot(well['GR'], well['DEPT'], color='red')
plt.ylim(max(well['DEPT']), min(well['DEPT']))
plt.title('Gamma Ray (GR)')
plt.grid()
plt.subplot(1, 5, 3)
plt.plot(well['RT'], well['DEPT'], color='blue')
plt.ylim(max(well['DEPT']), min(well['DEPT']))
plt.title('Resistivity (RT)')
plt.semilogx()
plt.grid()
plt.subplot(1, 5, 4)
plt.plot(well['RHOB'], well['DEPT'], color='orange')
plt.ylim(max(well['DEPT']), min(well['DEPT']))
plt.title('Density (RHOB)')
plt.grid()
plt.subplot(1, 5, 5)
plt.plot(well['NPHI'], well['DEPT'], color='purple')
plt.ylim(max(well['DEPT']), min(well['DEPT']))
plt.title('Neutron Porosity (NPHI)')
plt.grid()
# establecer espacio entre los registros de pozo
plt.tight_layout(1)
plt.show()
# tamaño del gráfico
plt.figure(figsize=(10,6))
# traza del crossplot (RHOB-NPHI-DEPTH)
plt.scatter( well['NPHI'], well['RHOB'], c = well['DEPT'])
plt.title('Neutron - Density Plot', size = 20)
plt.xlabel('NPHI (v/v)')
plt.ylabel('RHOB (g/cc)')
plt.colorbar()
plt.show()
Acceder al conjunto datos del historial de producción del Campo Volve en el Mar del Norte desde una base de datos disponible en Zenodo (Alfonso Reyes) y mostramos el plot de producción. Mayor información del conjunto de datos, ir a volve_eclipse_reservoir_v0.1.
# obtener el conjunto de datos del repositorio abierto (Zenodo-A.R.)
!wget 'https://zenodo.org/record/2596620/files/f0nzie/volve_eclipse_reservoir-v0.1.zip'
--2021-03-24 18:25:30-- https://zenodo.org/record/2596620/files/f0nzie/volve_eclipse_reservoir-v0.1.zip Resolving zenodo.org (zenodo.org)... 137.138.76.77 Connecting to zenodo.org (zenodo.org)|137.138.76.77|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 29500106 (28M) [application/octet-stream] Saving to: ‘volve_eclipse_reservoir-v0.1.zip’ volve_eclipse_reser 100%[===================>] 28.13M 4.98MB/s in 6.5s 2021-03-24 18:25:38 (4.35 MB/s) - ‘volve_eclipse_reservoir-v0.1.zip’ saved [29500106/29500106]
# descomprima el archivo y guárdelo en el directorio 'VolveData'
!unzip '/content/volve_eclipse_reservoir-v0.1.zip' -d '/content/Volve_Data'
Archive: /content/volve_eclipse_reservoir-v0.1.zip 413a669c3f9c66d77e0b6e527cd370d2bedfed0a creating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/ extracting: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/.Rbuildignore inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/.gitignore inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/README.Rmd inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/README.md creating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/ inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/balance_at-block.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/extract_step_info.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/field_totals_last_20rows.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/historical_production_cum_oil.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/material_balance_error_3001d.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/material_balance_errors.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/ooip.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/screenshot_volve_monthly.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_datasets.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_north_end.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_north_start.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_north_view.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_south_end.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_south_start.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_south_view.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_west_end.png inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/img/volve_west_start.png creating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/ creating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/other/ inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/other/field_totals.odt inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/other/regex-eclipse.odp creating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/rawdata/ inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/rawdata/VOLVE_2016.zip inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/rawdata/Volve production data.xlsx creating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/ inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/datafile.txt inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/examples_extracting_data_with_regex.Rmd inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/extract_data_from_text_file_examples.Rmd inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/extract_volve_field_totals.Rmd inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/extract_volve_simulation_steps.Rmd inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/extracting_volve_data-step_by_step.Rmd inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/notebooks/read_zip_file.Rmd inflating: /content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/volve_eclipse_reservoir.Rproj
# defina el directorio del archivo de los datos de producción del campo Volve
filepath = '/content/Volve_Data/f0nzie-volve_eclipse_reservoir-413a669/inst/rawdata/Volve production data.xlsx'
# leer el excel con el directorio definido
df = pd.read_excel(filepath, sheet_name='Monthly Production Data')
df
Wellbore name | NPDCode | Year | Month | On Stream | Oil | Gas | Water | GI | WI | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 15/9-F-4 | 5693.0 | 2007.0 | 9.0 | NaN | NaN | NaN | NaN | NaN | NaN |
1 | 15/9-F-5 | 5769.0 | 2007.0 | 9.0 | NaN | NaN | NaN | NaN | NaN | NaN |
2 | 15/9-F-4 | 5693.0 | 2007.0 | 10.0 | NaN | NaN | NaN | NaN | NaN | NaN |
3 | 15/9-F-5 | 5769.0 | 2007.0 | 10.0 | NaN | NaN | NaN | NaN | NaN | NaN |
4 | 15/9-F-4 | 5693.0 | 2007.0 | 11.0 | NaN | NaN | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
525 | 15/9-F-4 | 5693.0 | 2016.0 | 12.0 | NaN | NaN | NaN | NaN | NaN | NaN |
526 | NaN | NaN | NaN | NaN | hrs | Sm3 | Sm3 | Sm3 | Sm3 | Sm3 |
527 | NaN | NaN | NaN | NaN | NaN | 1.00371e+07 | 1.47537e+09 | 1.53186e+07 | NaN | NaN |
528 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
529 | NaN | NaN | NaN | NaN | From simulator | 9980819 | 1443979050 | 14400379 | NaN | NaN |
530 rows × 10 columns
# ver cuantos pozos distintos tiene el archivo excel
df['Wellbore name'].unique()
array(['15/9-F-4', '15/9-F-5', '15/9-F-12', '15/9-F-14', '15/9-F-11', '15/9-F-15 D', '15/9-F-1 C', nan], dtype=object)
# conjunto de datos solo del pozo 15/9-F-12
df[df['Wellbore name'] == '15/9-F-12']
Wellbore name | NPDCode | Year | Month | On Stream | Oil | Gas | Water | GI | WI | |
---|---|---|---|---|---|---|---|---|---|---|
10 | 15/9-F-12 | 5599.0 | 2008.0 | 2.0 | 406.625 | 49091.1 | 7.06801e+06 | 412.61 | NaN | NaN |
14 | 15/9-F-12 | 5599.0 | 2008.0 | 3.0 | 655.108 | 83361.3 | 1.21912e+07 | 27.42 | NaN | NaN |
18 | 15/9-F-12 | 5599.0 | 2008.0 | 4.0 | 613.647 | 74532.4 | 1.15064e+07 | 482.05 | NaN | NaN |
22 | 15/9-F-12 | 5599.0 | 2008.0 | 5.0 | 716.192 | 125479 | 1.90919e+07 | 16280.1 | NaN | NaN |
26 | 15/9-F-12 | 5599.0 | 2008.0 | 6.0 | 675.917 | 143787 | 2.15123e+07 | 474.37 | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
494 | 15/9-F-12 | 5599.0 | 2016.0 | 5.0 | 740.5 | 5865.13 | 925514 | 36147.8 | NaN | NaN |
500 | 15/9-F-12 | 5599.0 | 2016.0 | 6.0 | 703.258 | 5408.31 | 870533 | 34608.4 | NaN | NaN |
506 | 15/9-F-12 | 5599.0 | 2016.0 | 7.0 | 703.726 | 4533.82 | 729319 | 29940.2 | NaN | NaN |
512 | 15/9-F-12 | 5599.0 | 2016.0 | 8.0 | 285.892 | 1442.03 | 232558 | 10937.1 | NaN | NaN |
518 | 15/9-F-12 | 5599.0 | 2016.0 | 9.0 | 0 | 0 | 0 | 0 | NaN | NaN |
104 rows × 10 columns
# seleccionamos la data de los pozos 15/9-F-12
well_prod = df[df['Wellbore name'] == '15/9-F-12']
well_prod.reset_index(drop = True, inplace = True)
# definir los rates de cada fluido
oil_rate = well_prod['Oil']
gas_rate = well_prod['Gas']
water_rate = well_prod['Water']
# definir el tiempo en meses a partir de Febrero del 2008
t = np.arange(len(well_prod))
# tamaño del gráfico
plt.figure(figsize=(14, 7))
# traza de los datos de producción de los fluidos
plt.plot(t, oil_rate, label = 'Oil Production', lw = 2.4, color = 'green')
plt.plot(t, gas_rate, label = 'Gas Production', lw = 2.4, color = 'red')
plt.plot(t, water_rate, label = 'Water Production', lw = 2.4,color = 'blue')
plt.title('History Matching over Months Since February 2008 - 15/9-F-12', size=15)
plt.xlabel('Months Since February 2008', size=12)
plt.ylabel('Monthly Production (Sm3)', size=12)
plt.legend(fontsize = 'large')
plt.semilogy(True)
plt.ylim(10, 0.1e+9)
plt.grid(which="both", color = 'steelblue')
plt.show()
# graficar la producción de petróleo vs tiempo en meses
plt.figure(figsize=(12, 6))
# traza de los datos de producción del petróleo
plt.step(t, oil_rate, label = 'Well 15/9-F-12', lw = 2.4, color = 'green')
plt.title('Oil Monthly Production over Months Since February 2008', size=15)
plt.xlabel('Months Since February 2008', size=12)
plt.ylabel('Oil Monthly Production (Sm3)', size=12)
plt.axvspan(20, 82, color = 'lime', alpha = 0.25, lw = 2.5)
plt.grid(axis = 'y', color = 'steelblue')
plt.legend(fontsize = 'large')
plt.show()
# delimitar los datos de la región especificada
well = well_prod[20: 82]
# definir la producción y el tiempo
q = well['Oil']
t = np.arange(len(well['Oil']))
A continuación, hagamos el ajuste de la curva. En el ajuste de curva, siempre se recomienda normalizar nuestro conjunto de datos. El método más conocido en la normalización de datos, es el de dividir cada dato por su valor máximo.
# normalizar la producción y el tiempo
t_normalized = t / max(t)
q_normalized = q / max(q)
# definamos la función de la curva hiperbólica de Arps
def hyperbolic(t, qi, di, b):
return qi / (np.abs((1 + b * di * t))**(1/b))
Para ajustar la curva haremos uso de la biblioteca de Scipy. Desde este paquete importaremos curve_fit
.
# importar curve_fit desde scipy.optimize
from scipy.optimize import curve_fit
# encontrar los valores de qi, di, b
popt, pcov = curve_fit(hyperbolic, t_normalized, q_normalized)
print('Matriz de popt:\n', popt)
print('Matriz de pcov:\n', pcov)
Matriz de popt: [0.99230745 6.71901532 0.34950191] Matriz de pcov: [[0.00193631 0.02928725 0.00255265] [0.02928725 0.77851474 0.08673696] [0.00255265 0.08673696 0.01240239]]
Debido a que habíamos ajustado los datos normalizados, ahora necesitamos desnormalizar nuestros parámetros ajustados.
q=qi⋅qmax(1+b⋅ditmax⋅t)1/b# asignar los valores encontrados
qi, di, b = popt
# desnormalizamos qi y di
qi = qi * max(q)
di = di / max(t)
# imprima los valores: qi, di y b
print('Initial production rate:', np.round(qi, 3), 'Sm3')
print('Initial decline rate:', np.round(di, 3), 'Sm3/m')
print('Decline coefficient:', np.round(b, 3))
Initial production rate: 156786.086 Sm3 Initial decline rate: 0.11 Sm3/m Decline coefficient: 0.35
# ahora podemos pronosticar la tasa de producción
t_forecast = np.arange(64)
q_forecast = hyperbolic(t_forecast, qi, di, b)
Finalmente, graficamos nuestro resultado DCA (Decline Curve Analysis).
# graficar la producción de petróleo con los pronósticos
plt.figure(figsize=(12, 6))
plt.scatter(t, q, label = 'Production Data', color = 'darkblue')
plt.plot(t_forecast, q_forecast, label = 'Forecast', ls = '--', lw = 2.4, color = 'red')
plt.title('Oil Monthly Production (Well 15/9-F-12) - Result of DCA', size = 16, pad = 12)
plt.xlabel('Months Since February 2008', size = 12)
plt.ylabel('Oil Monthly Production (Sm3)', size = 12)
plt.grid(axis = 'y', color = 'steelblue')
plt.legend(fontsize = 'large')
plt.show()
SPWLA Student Chapter - Grupo de Investigación TRM