Pulse shapes

import matplotlib.pyplot as plt
import numpy as np
import scipy.signal

import sdr

%config InlineBackend.print_figure_kwargs = {"facecolor" : "w"}
# %matplotlib widget
span = 8  # Length of the pulse shape in symbols
sps = 10  # Samples per symbol

Create a rectangular pulse shape for reference.

rect = np.zeros(sps * span + 1)
rect[rect.size // 2 - sps // 2 : rect.size // 2 + sps // 2] = 1 / np.sqrt(sps)

Raised cosine

Create three raised cosine pulses with different excess bandwidths. This is achieved using the sdr.raised_cosine() function.

rc_0p1 = sdr.raised_cosine(0.1, span, sps)
rc_0p5 = sdr.raised_cosine(0.5, span, sps)
rc_0p9 = sdr.raised_cosine(0.9, span, sps)
plt.figure(figsize=(10, 5))
sdr.plot.impulse_response(rect, color="k", linestyle=":", label="Rectangular")
sdr.plot.impulse_response(rc_0p1, label=r"$\alpha = 0.1$")
sdr.plot.impulse_response(rc_0p5, label=r"$\alpha = 0.5$")
sdr.plot.impulse_response(rc_0p9, label=r"$\alpha = 0.9$")
plt.show()
../../_images/5f442354ac767658b7c7d3b52f561a369664dc71543c9c7aaefd36ca3f96b243.png

The raised cosine filter is a Nyquist filter. This means that the impulse response \(h[n]\) is zero at adjacent symbols. Specifically, \(h[n] = 0\) for \(n = \pm k\ T_s / T_{sym}\)

plt.figure(figsize=(10, 5))
sdr.plot.time_domain(np.roll(rc_0p1, -3 * sps))
sdr.plot.time_domain(np.roll(rc_0p1, -2 * sps))
sdr.plot.time_domain(np.roll(rc_0p1, -1 * sps))
sdr.plot.time_domain(np.roll(rc_0p1, 0 * sps))
sdr.plot.time_domain(np.roll(rc_0p1, 1 * sps))
plt.xlim(0, 60)
plt.title("Raised cosine pulses for adjacent symbols")
plt.tight_layout()
plt.show()
../../_images/a012abe85873b8a1b48595ac3a046864f25e8f6b995556084e0eb162fca5afb7.png
plt.figure(figsize=(10, 5))
sdr.plot.frequency_response(rect, sample_rate=sps, color="k", linestyle=":", label="Rectangular")
sdr.plot.frequency_response(rc_0p1, sample_rate=sps, label=r"$\alpha = 0.1$")
sdr.plot.frequency_response(rc_0p5, sample_rate=sps, label=r"$\alpha = 0.5$")
sdr.plot.frequency_response(rc_0p9, sample_rate=sps, label=r"$\alpha = 0.9$")
plt.xlim(-3, 3)
plt.ylim(-70, 20)
plt.xlabel("Normalized frequency, $f/f_{sym}$")
plt.show()
../../_images/dce892e02798f8b4574477c28aee27d0e42ba62aaf1d52e40aa28cf5de80b1cf.png

Notice the raised cosine pulse with excess bandwidth \(\alpha = 0.1\) has a total bandwidth of nearly \(f_{sym}\). Compare this to \(\alpha = 0.9\), which has a null-to-null bandwidth of nearly \(2 f_{sym}\).

While small \(\alpha\) produces a filter with smaller bandwidth, its side lobes are much higher.

# Compute the one-sided power spectral density of the pulses
w, H_rect = scipy.signal.freqz(rect, 1, worN=1024, whole=False, fs=sps)
w, H_rc_0p1 = scipy.signal.freqz(rc_0p1, 1, worN=1024, whole=False, fs=sps)
w, H_rc_0p5 = scipy.signal.freqz(rc_0p5, 1, worN=1024, whole=False, fs=sps)
w, H_rc_0p9 = scipy.signal.freqz(rc_0p9, 1, worN=1024, whole=False, fs=sps)

# Compute the relative power in the main lobe of the pulses
P_rect = 10 * np.log10(np.cumsum(np.abs(H_rect) ** 2) / np.sum(np.abs(H_rect) ** 2))
P_rc_0p1 = 10 * np.log10(np.cumsum(np.abs(H_rc_0p1) ** 2) / np.sum(np.abs(H_rc_0p1) ** 2))
P_rc_0p5 = 10 * np.log10(np.cumsum(np.abs(H_rc_0p5) ** 2) / np.sum(np.abs(H_rc_0p5) ** 2))
P_rc_0p9 = 10 * np.log10(np.cumsum(np.abs(H_rc_0p9) ** 2) / np.sum(np.abs(H_rc_0p9) ** 2))

plt.figure(figsize=(10, 5))
plt.plot(w, P_rect, color="k", linestyle=":", label="Rectangular")
plt.plot(w, P_rc_0p1, label=r"$\alpha = 0.1$")
plt.plot(w, P_rc_0p5, label=r"$\alpha = 0.5$")
plt.plot(w, P_rc_0p9, label=r"$\alpha = 0.9$")
plt.legend()
plt.xlim(0.25, 1)
plt.ylim(-3, 0)
plt.grid()
plt.xlabel("One-sided normalized frequency, $f/f_{sym}$")
plt.ylabel("Relative power (dB)")
plt.title("Relative power within bandwidths for various raised cosine pulses")
plt.tight_layout()
plt.show()
../../_images/33f865ac71d7f6eb3b3ddb5b76f83112f544aac5dbb19f2635e52a0d7f141dad.png

Square-root raised cosine

Create three square-root raised cosine pulses with different excess bandwidths. This is achieved using the sdr.root_raised_cosine() function.

srrc_0p1 = sdr.root_raised_cosine(0.1, span, sps)
srrc_0p5 = sdr.root_raised_cosine(0.5, span, sps)
srrc_0p9 = sdr.root_raised_cosine(0.9, span, sps)
plt.figure(figsize=(10, 5))
sdr.plot.impulse_response(rect, color="k", linestyle=":", label="Rectangular")
sdr.plot.impulse_response(srrc_0p1, label=r"$\alpha = 0.1$")
sdr.plot.impulse_response(srrc_0p5, label=r"$\alpha = 0.5$")
sdr.plot.impulse_response(srrc_0p9, label=r"$\alpha = 0.9$")
plt.legend()
plt.show()
../../_images/3090e72315713516e6ac4a64a6fba0753ebec38bf164935ae9c954c5465edaa1.png

The square-root raised cosine filter is not a Nyquist filter. Therefore, the impulse response \(h[n]\) is not zero at adjacent symbols.

plt.figure(figsize=(10, 5))
sdr.plot.time_domain(np.roll(srrc_0p1, -3 * sps))
sdr.plot.time_domain(np.roll(srrc_0p1, -2 * sps))
sdr.plot.time_domain(np.roll(srrc_0p1, -1 * sps))
sdr.plot.time_domain(np.roll(srrc_0p1, 0 * sps))
sdr.plot.time_domain(np.roll(srrc_0p1, 1 * sps))
plt.xlim(0, 60)
plt.title("Square-root raised cosine pulses for adjacent symbols")
plt.tight_layout()
plt.show()
../../_images/f535ce1919c5dac129997b178c0f1c7988ede311f9ac42258cb4c0e71ba23a71.png
plt.figure(figsize=(10, 5))
sdr.plot.frequency_response(rect, sample_rate=sps, color="k", linestyle=":", label="Rectangular")
sdr.plot.frequency_response(srrc_0p1, sample_rate=sps, label=r"$\alpha = 0.1$")
sdr.plot.frequency_response(srrc_0p5, sample_rate=sps, label=r"$\alpha = 0.5$")
sdr.plot.frequency_response(srrc_0p9, sample_rate=sps, label=r"$\alpha = 0.9$")
plt.legend()
plt.xlim(-3, 3)
plt.ylim(-70, 20)
plt.xlabel("Normalized frequency, $f/f_{sym}$")
plt.show()
../../_images/76846d3f254a9b9ddf4503c95a47f28a2adbbd57b53a41d8d88c62fadcff04ca.png

While the bandwidths of the square-root raised cosine filter are similar to the raised cosine filter, the side lobes are significantly higher. This is due to this filter not being a Nyquist filter.

# Compute the one-sided power spectral density of the pulses
w, H_rect = scipy.signal.freqz(rect, 1, worN=1024, whole=False, fs=sps)
w, H_srrc_0p1 = scipy.signal.freqz(srrc_0p1, 1, worN=1024, whole=False, fs=sps)
w, H_srrc_0p5 = scipy.signal.freqz(srrc_0p5, 1, worN=1024, whole=False, fs=sps)
w, H_srrc_0p9 = scipy.signal.freqz(srrc_0p9, 1, worN=1024, whole=False, fs=sps)

# Compute the relative power in the main lobe of the pulses
P_rect = 10 * np.log10(np.cumsum(np.abs(H_rect) ** 2) / np.sum(np.abs(H_rect) ** 2))
P_srrc_0p1 = 10 * np.log10(np.cumsum(np.abs(H_srrc_0p1) ** 2) / np.sum(np.abs(H_srrc_0p1) ** 2))
P_srrc_0p5 = 10 * np.log10(np.cumsum(np.abs(H_srrc_0p5) ** 2) / np.sum(np.abs(H_srrc_0p5) ** 2))
P_srrc_0p9 = 10 * np.log10(np.cumsum(np.abs(H_srrc_0p9) ** 2) / np.sum(np.abs(H_srrc_0p9) ** 2))

plt.figure(figsize=(10, 5))
plt.plot(w, P_rect, color="k", linestyle=":", label="Rectangular")
plt.plot(w, P_srrc_0p1, label=r"$\alpha = 0.1$")
plt.plot(w, P_srrc_0p5, label=r"$\alpha = 0.5$")
plt.plot(w, P_srrc_0p9, label=r"$\alpha = 0.9$")
plt.legend()
plt.xlim(0.25, 1)
plt.ylim(-3, 0)
plt.grid()
plt.xlabel("One-sided normalized frequency, $f/f_{sym}$")
plt.ylabel("Relative power (dB)")
plt.title("Relative power within bandwidths for various square-root raised cosine pulses")
plt.tight_layout()
plt.show()
../../_images/bcc9a24056afb554b42f532939a869945850e72594e6efd3ef1f0db22b3a6b42.png

Gaussian

Create three raised Gaussian pulses with different time-bandwidth products. This is achieved using the sdr.gaussian() function.

gauss_0p1 = sdr.gaussian(0.1, span, sps)
gauss_0p2 = sdr.gaussian(0.2, span, sps)
gauss_0p3 = sdr.gaussian(0.3, span, sps)
plt.figure(figsize=(10, 5))
sdr.plot.impulse_response(gauss_0p1, label=r"$B T_{sym} = 0.1$")
sdr.plot.impulse_response(gauss_0p2, label=r"$B T_{sym} = 0.2$")
sdr.plot.impulse_response(gauss_0p3, label=r"$B T_{sym} = 0.3$")
plt.show()
../../_images/38dab7fa6c1f34f5fbda7334a9f862f379d0344d976d0985300271ff9d37b888.png
plt.figure(figsize=(10, 5))
sdr.plot.frequency_response(gauss_0p1, sample_rate=sps, label=r"$B T_{sym} = 0.1$")
sdr.plot.frequency_response(gauss_0p2, sample_rate=sps, label=r"$B T_{sym} = 0.2$")
sdr.plot.frequency_response(gauss_0p3, sample_rate=sps, label=r"$B T_{sym} = 0.3$")
plt.legend()
plt.xlabel("Normalized frequency, $f/f_{sym}$")
plt.show()
../../_images/ddc163679798c2f29ff02ada626c20deadf2b5e20daeba77919ad2fc9a04b731.png

Last update: Jul 15, 2023