Getting Started

To run this notebook you will first need to download the GW data,

python download_SXSwaveform_data.py

[1]:
import numpy as np
import matplotlib.pyplot as plt
import WDM

Here’s an example GW waveform. We also compute the frequency of the signal as a function of time.

[2]:
data = np.loadtxt("../data/waveform_SXS:BBH:0305_Reh22.txt")

time = data[0]
re_strain = data[1]
im_strain = data[2]

frequency = np.diff(-np.unwrap(np.atan2(im_strain, re_strain)), prepend=0) / np.diff(time, prepend=0) / (2*np.pi)

fig, axes = plt.subplots(nrows=2, sharex=True)
axes[0].plot(time, re_strain)
axes[0].set_xlabel('Time')
axes[0].set_ylabel('Strain')
axes[0].grid()
axes[1].plot(time[100:-200], frequency[100:-200], 'k-')
axes[1].set_xlabel('Time')
axes[1].set_ylabel('Frequency')
axes[1].grid()
plt.show()
_images/getting_started_3_0.png
[3]:
current_length = len(time)

print(f"Current length of time series: {current_length}")
Current length of time series: 7688

Let’s take a wavelet transform of this using \(N_f=512\) frequency slices. The wdm object will handle the wavelet transform. We need to pad the signal so that it’s length is an even multiple of this number

[5]:
dt = np.mean(np.diff(time))

Nf = 512

N = WDM.utils.next_multiple(current_length, 2*Nf)

wdm = WDM.WDM.WDM_transform(dt=dt, Nf=Nf, N=N, q=8, calc_m0=True)

re_strain_padded, mask = WDM.utils.pad_signal(re_strain, N)
im_strain_padded, mask = WDM.utils.pad_signal(im_strain, N)

print(f"Padded length of time series: {N}")

Padded length of time series: 8192

We can print the wdm object to see some key information about the wavelets we are using.

[6]:
print(wdm)
WDM_transform(Nf=512, N=8192, q=8, d=4, A_frac=0.25, calc_m0=True)
self.Nt = 16 time cells
self.Nf = 512 frequency cells
self.dT = 256.0 time resolution
self.dF = 0.001953125 frequency resolution
self.K = 8192 window length

Take the forward discrete wavelet transform (dwt). This transforms from the time domain to the time-frequency domain.

[7]:
w = wdm.dwt(re_strain_padded)

We can plot these coefficients in the time-frequency plane. The black line shows the frequency obtained directly from the NR waveform. Note, this doesn’t look as beautifully smooth as the oversampled periodograms we are used to looking at, but this is what we want for data analysis.

[8]:
fig, ax = WDM.plotting.time_frequency_plot(wdm, w, part='abs', scale='linear')
ax.set_ylim(0,0.1)
ax.plot(time[100:-200], frequency[100:-200], color='k', lw=2)
plt.show()
_images/getting_started_12_0.png

Let’s take an inverse discrete wavelet transform (idwt).

This transforms back into the time domain. We do this to check we can recover the original signal. We will use the mask to get the output back to the original shape.

[9]:
re_strain_recovered = wdm.idwt(w)[mask]

residuals = re_strain_recovered-re_strain

print(f"Max residual: {np.max(np.abs(residuals))}")
Max residual: 3.087964996553727e-14
[10]:
fig, ax = plt.subplots()
ax.plot(time, re_strain, label='SXS:BBH:0305')
ax.plot(time, re_strain_recovered, label='recovered')
ax.plot(time, residuals, 'k:', label='residuals')
ax.set_xlabel('Time (s)')
ax.set_ylabel('Strain')
ax.legend()
ax.grid()
plt.show()
_images/getting_started_15_0.png

The operations for the forward and inverse discrete wavelet transform are vectorised for multiple transforms to be performed at once in batches.

We demonstrate this here by transforming both + and \(\times\) components of the GW signal (batch size=2).

[11]:
GW_signal_padded = np.vstack((re_strain_padded, re_strain_padded))

print(f"Time series shape = {GW_signal_padded.shape}")

GW_w = wdm.dwt(GW_signal_padded)

print(f"Wavelet coefficients shape = {GW_w.shape}")

GW_signal_recovered = wdm.idwt(GW_w)

print(f"Recovered time series shape = {GW_signal_padded.shape}")
Time series shape = (2, 8192)
Wavelet coefficients shape = (2, 16, 512)
Recovered time series shape = (2, 8192)
[ ]: