Generating Synthetic Seismogram in Python | by Ryan A. Mardani | Towards Data Science

One of the most fundamental concepts in geophysics is convolution. Seismic data that we record on geophysical operations are the reflected energy from the Earth’s internal surfaces that have different physical rock properties from adjacent layers. In fact, seismic signature results from the convolution of reflectivity of layers with energy source wavelet.
Generating synthetic seismogram from well logs is a modeling process. We will convolve reflectivity series (derived from logs) with zero phase wavelet such as Ricker wavelet. We need seismograms to tie well data to seismic data for interpretation tasks.
In this paper, I will try to add some programing fun into the synthetic seismogram creation process. I really like python because of various libraries that can make the tasks much easier than other programming platforms.I have uploaded a full jupyter notebook file and raw data in my GitHub account if you what to try it by yourself. We will deal with log data with las format. If you are unfamiliar with the procedure on how to read las files into python, please review my previous post.
Required steps to generate seismogram:1- Data Preparation (Unit conversion and Sonic / Density Logs processing)2- Time-Depth relationship construction3- Acoustic Impedance(AI or Z) Calculation4- Reflection Coefficient(Rc) calculation5- Resampling to the time domain and Rc recalculation6- Wavelet & Convolution7- Visualization
After reading a well log file (las format) into python and looking at the log header, we need to convert the units of Density and Sonic logs to SI system because we need these two logs for impedance calculation. To calculate Acoustic Impedance, we need to have Sonic log (DT) in μs/m unit as well as density log (RHOB) in kg/m3. Looking at header: DT has us/ft unit and RHOB g/cm3.
w.data['DT'] = w.data['DT'] / 0.3048          #unit convert to μs/mw.data['RHOB'] = w.data['RHOB'] * 1000        #unit convert to kg/m3
Spikes are common on well logs, especially on sonic measurements. These spikes are not the true representative of rock properties variation through the well intervals and need to be eliminated.
#Sonic Despikingdt = w.data['DT']w.data['DT_DS'] = dt.despike(window_length=50, z=2)#Density Despikingden = w.data['RHOB']w.data['RHOB_DS'] = den.despike(window_length=50, z=2)
We will input despiked data to be smoothed:
#Sonic Smoothingdt_ds = w.data['DT_DS']w.data['DT_DS_SM'] = dt_ds.smooth(window_length=10, samples=False)#Density Smoothingden_ds = w.data['RHOB_DS']w.data['RHOB_DS_SM'] = den_ds.smooth(window_length=10, samples=False)
Seismic data is measured in the time domain while well data mainly have depth domain. To convert time to depth and vice versa, we need to establish a relationship. There are several ways to do this but here, as we use the sonic log, which is reciprocal of sound wave velocity. Mathematically if we integrate DT over depth interval, we will compute the time-to-depth relationship. Before that, we should make a remedy for the upper part log data which has a data gap from the surface. As this is on-shore field data, we do not have a water column.The replacement velocity for the section above log is almost 2600 m/s. Simply, dividing gapped interval thickness (log start depth subtracted from kb) by replacement velocity will give us the time.
log_start = 1517          # Depth of logging starts(m) from headerkb = 15                   # Kelly Bushing elevation(m) from headergap_int = log_start - kbrepl_vel = 2632           # this is from VSP data knowledge (m/s)log_start_time = 2.0 * gap_int / repl_vel        # 2 for twt
Let’s implement integration. As depth sampling interval is 0.1524 meter, we need to calculate the time that takes for that thickness to travel with measured sonic data. Then calculate cumulative travel time to downward. Calculating from log starting depth shows zero for first value while we know zero time belongs to the ground surface. So, the travel time of this gap should be added to integrated sonic time.
#first replace NaN values with zerodt_iterval = np.nan_to_num(dt) * 0.1524 / 1e6t_cum =  np.cumsum(dt_iterval) * 2w.data['TWT'] = t_cum + log_start_time
Acoustic Impedance(AI, sometimes showed by Z) is defined as the product of sonic wave velocity and medium density. In reflection seismology, AI variation between geological layers is responsible for the energy reflection toward the surface where we collect seismic data.
df['Vsonic'] = 1e6/df.DT_DS_SM                   #(unit: m/s)df['AI'] = df['Vsonic'] * df['RHOB_DS_SM']        #(unit: kg/m2.s)
Reflection Coefficient (Rc) is defined as impedance differences of two adjacent layers divided by their sum. Mathematically, this is derivative with respect to depth.
Imp = df['AI'].valuesRc=[]for i in range(len(Imp)-1):    Rc.append((Imp[i+1]-Imp[i])/(Imp[i]+Imp[i+1]))
Although we established a time-depth relationship(TDR), the components that we need for synthetic seismogram are in depth domain. The reflectivity series should be transferred into a time domain for convolution. After defining the time vector, we can use the interpolation function from the numpy library.
dt = 0.001   #sampleing intervalt_max = 3.0   # max time to create time vectort = np.arange(0, t_max, dt)AI_tdom = np.interp(x=t, xp = df.TWT, fp = df.AI)    #resampling# again Rc calulation but in reampled time domainRc_tdom = []for i in range(len(AI_tdom)-1):    Rc_tdom.append((AI_tdom[i+1]-AI_tdom[i])/(AI_tdom[i]+AI_tdom[i+1]))
In reflection seismology, it is assumed that the seismic data we record on the surface is the reflection of energy which is convolved (masked) with wavelet. From various wavelets, Ricker is one of the most common types as it is zero phase and favorable for seismic interpretation tasks.
# define function of ricker waveletdef ricker(f, length, dt):    t0 = np.arange(-length/2, (length-dt)/2, dt)    y = (1.0 - 2.0*(np.pi**2)*(f**2)*(t0**2)) * np.exp(-(np.pi**2)*(f**2)*(t0**2))    return t0, y
In this step, we will convolve the reflectivity series with a wavelet to create a synthetic seismogram. It is commonly known as flip and slide a function over the other one. If you are interested to know the mathematic behind this operation you may look at this great video.
f=20            #wavelet frequencylength=0.512    #Wavelet vector lengthdt=dt           # Sampling prefer to use smiliar to resampled AIt0, w = ricker (f, length, dt) # ricker wavelet synthetic = np.convolve(w, Rc_tdom, mode='same')
In this figure, we will plot the well logs (overlaid with smoothing and despiking results) and calculated AI and Reflectivity in depth domain. For plotting procedure in python, refer to the notebook file here.
In this figure, we will plot AI and convolution results in the time domain. We also have access to real surface seismic traces around a well that can juxtapose with synthetic seismogram to see how match with that data.
Looking at the last track shows that there is an appropriate match between synthetics seismogram and surface seismic data especially in key horizons. There are a lot of arguments that why the match can be identical. One important reason is the frequency band that the surface seismic and sonic log have. If you are interested to study more, please refer to my professor's book, Laurence R. Lines.
In reflection seismology, synthetic seismogram is based on convolution theory. Seismograms are a very important tool for seismic interpretation where they work as a bridge between well and surface seismic data. In this post, I have tried to show how we can implement this task in some lines of code with real data in python. Please feel free to download source codes, look around and implement for your own data. For comments and questions leave a comment here.