Well Test Analysis With Python
"A Data Analytics based Approach"
Divyanshu Vyas | Machine Learning Consultant (DiceLytics)
My GitHub : https://github.com/Divyanshu-ISM/Oil-and-Gas-data-analysis (https://github.com/Divyanshu-ISM/Oil-and-Gas-data-analysis)
My LinkedIn : www.linkedin.com/in/divyanshu-vyas
1. First, A Bit of Theory and Back-ground Mathematics
Concection with Real World - Suppose you have a metalic slab, and you heat it at a point with the candle. Now, the heat travels further into the reservoir(the slab) as the entire slab gets heated.
This distribution (Temperature travelling as a function of (r,t) is given by the Following Diffusivity equation-
2
∂ T
2
= α
∂T
............. (The HEAT EQN)
∂x ∂t
This is exactly what happens in the reservoir.
The Source of disturbance (the candle) is our Well, and the travelling entity is the Pressure (rather than temperature).
1.1 The Pressure Diffusivity equation
It is Given as follows-
r
∂
(r
∂P
) =
1
η
∂P
............. (The DIFFUSIVITY EQN)
∂r ∂r ∂t
This Equation gives us the Solutions that describe the way the pressure travels in the reservoir as soon as a disturbance (A rate change or a shut in or anything) is initiated in it.
2. The Main Game - Constant Rate Pressure Transient Analysis
162.6qμB k
Pwf = Pi − [log(t) + log( ) − 3.23 + 0.87 ∗ S]
kh ϕμcrw 2
Pwf = −m ∗ log(t) + C ..... (SEMI-LOG Straight Line)
162.6qμB
Where, m =
kh
So, basically this equation will be used. Here, the Slope, m is important. The Slope of the Semi-log straight line gives us the Permeability K.
However, this Straight Line is applicable only to the Pressure Data belonging to the MTR (Infinite Acting Radial Flow) Domain.
So, Our First Task is to Locate the IARF Domain. We use the Log-Log Analysis for that.
3. The Log-Log (Derivative) Plot for Diagnosis
′ ∂△P ∂△P
P = = t ∗
∂ln(t) ∂t
70.6qμB
Since, △P =
kh
ln(t) + c
′ 70.6qμB
P =
kh
Hence, in the Log-Log Plot, a horizontal plateau corresponds to the IARF domain. The ordinate of this plateau gives K.
Let's do an Excercise to see how we can do this in Python.
Excercise : Calculating formation K (mD) using Well Test Data
Note :- I'll be using Python's Data Analysis and Visualization Libraries for this.
1. NumPy - Calculations and Scientific Computations (Logs, Trigonometry etc)
2. Pandas - Storing Data in Tables. Tabular Data Manipulations.
3. Matplotlib - For Visualisations.
PROBLEM STATEMENT :-
A drawdown test is perfomed in a well. During the test, the well was produced at a stabilized rate of 250 stb/d for a period of 460 hrs. The initial reservoir pressure was 4412 psia. The recorded flowing bottomhole pressure versus time is given.
𝐹𝑜𝑟𝑚𝑎𝑡𝑖𝑜𝑛 𝑡ℎ𝑖𝑐𝑘𝑛𝑒𝑠𝑠=69 𝑓𝑡
𝑃𝑜𝑟𝑜𝑠𝑖𝑡𝑦=3.9%
𝑇𝑜𝑡𝑎𝑙 𝐶𝑜𝑚𝑝𝑟𝑒𝑠𝑠𝑖𝑏𝑖𝑙𝑖𝑡𝑦=17∗〖10〗^(−6) 〖𝑝𝑠𝑖〗^(−1)
𝑂𝑖𝑙 𝑓𝑜𝑟𝑚𝑎𝑡𝑖𝑜𝑛 𝑣𝑜𝑙𝑢𝑚𝑒 𝑓𝑎𝑐𝑡𝑜𝑟=1.136 rb/𝑠𝑡𝑏, 𝑂𝑖𝑙 𝑣𝑖𝑠𝑐𝑜𝑠𝑖𝑡𝑦=0.8 𝑐𝑝, 𝑊𝑒𝑙𝑙𝑏𝑜𝑟𝑒 𝑟𝑎𝑑𝑖𝑢𝑠=0.198 𝑓𝑡
Interpret the drawdown test.
/
In [1]: #STEP 1 : Summon All the Computational Superpowers.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# COREY SCHAFFER
# KRISH NAIK
# SentDex
# SOLOLEARN
# W3schools.com
# 1. Python Basics (COREY Schaffer)
# 2. NumPy
# 3. Pandas
# 4. Matplotlib
# 5. Machine Learning :KRISH NAIK , 3Blue1Brown, Khan Academy
#DATASETS- 1. Kaggle 2. SEG Wiki 3. Volve
In [2]: #STEP : Initialize and Assign values.
h = 69 #ft
phi = 0.039 #fraction
q = 250 #stb/d
B = 1.136 #rb/stb
mu = 0.8 #cp
rw = 0.198 #ft
ct = 17*(10**-6) #psi-1
In [3]: #STEP 2 : Load the Dataset (Import) into a Pythonic Table - DataFrame
df = pd.read_csv('DRAWDOWN.csv',sep='\t')
In [4]: #STEP 3 : Look at the Data. Get a Feel of it.
df
Out[4]:
t (hrs) FBHP (psia)
0 0.00 4412
1 1.60 3812
2 1.94 3699
3 2.79 3653
4 4.01 3636
5 4.82 3616
6 5.78 3607
7 6.94 3600
8 8.32 3593
9 9.99 3586
10 14.40 3573
11 17.30 3567
12 20.70 3561
13 24.90 3555
14 29.80 3549
15 35.80 3544
16 43.00 3537
17 51.50 3532
18 61.80 3526
19 74.20 3521
20 89.10 3515
21 107.00 3509
22 128.00 3503
23 154.00 3497
24 185.00 3490
25 222.00 3481
26 266.00 3472
27 319.00 3460
28 383.00 3446
29 460.00 3429
/
In [5]: #STEP 3 : First Visualisation
plt.style.use('default')
plt.figure(figsize=(8,4))
plt.plot(df['t (hrs)'],df['FBHP (psia)'] )
plt.xlabel('Time(hrs)')
plt.ylabel('Pwf(psia)')
plt.title('Cartesian Plot for Drawdown Testing. Nothing Evident.')
plt.grid()
In [6]: #STEP 4 :- Road to the Log-Log Plotting. Diagnosis for MTR (IARF) begins.
# df.head()
Pi = 4412 #psia
df['delta P'] = Pi - df['FBHP (psia)']
df['delta t'] = np.zeros(len(df))
df['delta t'][1:] = np.diff(df['t (hrs)'])
df['d(delta P)'] = np.zeros(len(df))
df['d(delta P)'][1:] = np.diff(df['delta P'])
df['P\''] = df['t (hrs)']*df['d(delta P)']/df['delta t']
df.head()
<ipython-input-6-fb199c33c619>:10: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df['delta t'][1:] = np.diff(df['t (hrs)'])
<ipython-input-6-fb199c33c619>:14: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df['d(delta P)'][1:] = np.diff(df['delta P'])
Out[6]:
t (hrs) FBHP (psia) delta P delta t d(delta P) P'
0 0.00 4412 0 0.00 0.0 NaN
1 1.60 3812 600 1.60 600.0 600.000000
2 1.94 3699 713 0.34 113.0 644.764706
3 2.79 3653 759 0.85 46.0 150.988235
4 4.01 3636 776 1.22 17.0 55.877049
In [7]: plt.style.use('default')
plt.figure(figsize=(8,6))
plt.loglog(df['t (hrs)'],df['P\''],marker='o',label='Derivative Plot')
plt.loglog(df['t (hrs)'],df['delta P'],marker='x',label='$ \delta P $')
plt.ylim(10,1500)
plt.xlabel('time(hrs)')
plt.ylabel('Pressure Family')
plt.legend(loc='right')
plt.grid()
/
A). Infinite Acting Radial Flow Domain (MTR)
Let's Perform some analysis on this plot.
In [8]: def plotloglog():
plt.style.use('default')
plt.figure(figsize=(8,6))
plt.loglog(df['t (hrs)'],df['P\''],marker='o',label='Derivative Plot')
plt.loglog(df['t (hrs)'],df['delta P'],marker='x',label='$ \delta P $',color='brown')
plt.ylim(5,1500)
plt.xlabel('time(hrs)')
plt.ylabel('Pressure Family')
#Analysis-
plt.axvspan(1,10,color='grey',alpha=0.2,label='WBS')
plt.axvspan(10,100,color='gold',alpha=0.3,label='IARF')
plt.axvspan(100,1000,color='red',alpha=0.3,label='PSS')
plt.axhline(35,lw=5,color='black')
plt.title('Annotated Log-Log Plot for Drawdown Analysis.')
plt.legend(loc='lower left')
plt.grid()
In [9]: plotloglog()
Conclusion :- It can be seen that the Yellow zone corresponds to IARF.
t = 10 hrs to t = 100 hrs
In [10]: # df.head()
t_iarf = df[(df['t (hrs)']>=10) & (df['t (hrs)']<=100)]['t (hrs)']
pwf_iarf = df[(df['t (hrs)']>=10) & (df['t (hrs)']<=100)]['FBHP (psia)']
In [11]: # plt.scatter(t_iarf,np.log(pwf_iarf))
def iarf_semilogplot():
sns.regplot(np.log10(t_iarf),pwf_iarf)
plt.title('Semi-Log Analysis for the IARF Domain.')
plt.grid()
/
In [12]: iarf_semilogplot()
In [13]: #Now Since the Slope of this line gives us Permeability. Let's Evaluate the Slope.
m , c = np.polyfit(x=np.log10(t_iarf), y=pwf_iarf, deg=1)
print(f'Results of Semi-Log Analysis- \n1. Slope (m) = {np.abs(m)} \n2. Intercept(c) = {c}')
Results of Semi-Log Analysis-
1. Slope (m) = 73.19674289918875
2. Intercept(c) = 3657.378934062456
In [ ]:
162.6qμB
m =
kh
In [14]: k = np.abs(162.6*q*mu*B/m/h)
print(f'Results of Semi-Log Analysis- \n1. Slope (m) = {np.abs(m)} \n2. Intercept(c) = {c} \n& Hence, Permeability(K) = {k} mD.')
Results of Semi-Log Analysis-
1. Slope (m) = 73.19674289918875
2. Intercept(c) = 3657.378934062456
& Hence, Permeability(K) = 7.314556876770109 mD.
Two Methods to calculate K -
1. Semilog Plot. (Done)
2. log-log Plot. (Next up)
In [15]: plotloglog()
In [16]: #Looks like the Plateau (Yellow Shaded) is around 30 to 35. Lets Go for 32
y_plateau = 32
# y_plateau = 70.6quB/kh
k_loglog = 70.6*q*mu*B/y_plateau/h
k_loglog
Out[16]: 7.264637681159419
Conclusion-
Semilog Analysis : K = 7.31 mD
Log-Log Plot : K = 7.26 mD
In [17]: ###########################################################################
/
In [18]: plotloglog()
Looks Like for t>=110 hrs (Red Shaded) There's a Unit Slope Line. Possible occurence of a Pseudo Steady State.
B). Semi Steady State (LTR) Analysis
In [19]: t_pss = df[df['t (hrs)'] >= 110]['t (hrs)']
pwf_pss = df[df['t (hrs)'] >= 110]['FBHP (psia)']
If Our Estimate is True, the cartesian plot of Pwf_pss vs t_pss must give a straight line.
For Semi-Steady State,
dPwf qB
= −0.234
dt ϕcAh
Hence, the slope of the PSS straight line(Cartesian) gives us Ahϕ which is the Pore Volume.
In [20]: def pss_cartplot():
sns.regplot(t_pss,pwf_pss,color='maroon')
plt.title('Cartesian Plot for Semi Steady state')
plt.grid()
In [21]: pss_cartplot()
In [22]: # Calculating the Areal extent (A):
pss_coeffs = np.polyfit(x=t_pss,y=pwf_pss,deg=1)
m_pss = np.abs(pss_coeffs[0])
A = (0.234*q*B/phi/ct/m_pss/h)/43560
In [23]: print(f'The Area of the Reservoir is {A} acres.')
The Area of the Reservoir is 149.96295697014455 acres.
/
In [24]: plotloglog()
C). Wellbore Stroage (WBS) Analysis
In [25]: t_wbs = df[df['t (hrs)'] <=2]['t (hrs)']
pwf_wbs = df[df['t (hrs)'] <= 2]['FBHP (psia)']
In [26]: def wbs_cartplot():
sns.regplot(t_wbs,pwf_wbs,color='green',ci=20)
plt.title('Cartesian Plot For WBS : Gives Cs')
plt.grid()
In [27]: wbs_cartplot()
In [28]: coeffs_wbs = np.polyfit(x=t_wbs,y=pwf_wbs,deg=1)
m_wbs = np.abs(coeffs_wbs[0])
dPwf qB
mwbs = = −
dt 24Cs
In [29]: Cs = q*B/(24*m_wbs)
In [30]: print(f'The Welbore Storage Coefficient Cs = {Cs} bbls/psi.')
The Welbore Storage Coefficient Cs = 0.03199358425286778 bbls/psi.
In [31]: ##############################
In [32]: ##############################
Summary-
1. First Domain that Occurs - Wellbore Storage (WBS).
Identification :- Grey Shaded Zone : Unit Slope Line in Derivative (Log Log) Plot.
Analysis :- Cartesian Plot of Pwf vs t gives us Cs
/
In [33]: plotloglog()
In [40]: wbs_cartplot()
2. Second Domain that Occurs - Infinite Acting Radial Flow (IARF).
Identification :- Gold Shaded Zone : Horizontal Plateau in Derivative (Log Log) Plot.
Analysis :- Semilog Plot : Pwf vs log(t) :- slope gives K.
In [41]: plotloglog()
/
In [42]: iarf_semilogplot()
3. Third Domain that Occurs - Semi-Steady State (PSS).
Identification :- Red Shaded Zone : Unit Slope Line in Derivative (Log Log) Plot.
Analysis :- Cartesian Plot : Pwf vs t :- Gives A (Drainage Area of the well).
In [43]: plotloglog()
In [44]: pss_cartplot()
In [39]: ###########################End###########################################
/
Sources (YouTube)
1. COREY SCHAFFER
2. KRISH NAIK
3. SentDex
1. SOLOLEARN
2. W3schools.com
ORDER To Learn :-
1. Python Basics (COREY Schaffer)
2. NumPy
3. Pandas
4. Matplotlib
5. Machine Learning :KRISH NAIK , 3Blue1Brown, Khan Academy
DATASETS- 1. Kaggle 2. SEG Wiki 3. Volve
In [ ]: