November 15, 2024

How to Create Bode Plots of Transfer Functions in Python Using SciPy – Control Engineering Tutorial

In this control engineering and control theory tutorial, we explain how to generate Bode plots of transfer functions in Python using the SciPy library. We also provide a YouTube tutorial. The YouTube tutorial explaining all the steps is given below.

First of all, open a terminal and make sure that SciPy, matplotlib, and NumPy libraries are installed. To do that, type

pip install scipy
pip install numpy 
pip install matplotlib

Next, open a new Python script. The first step is to import the necessary libraries:

import matplotlib.pyplot as plt

from scipy import signal

import numpy as np

To define transfer functions, we will use SciPy’s signal processing library called scipy.signal. The first step is to define a transfer function. In this tutorial, as a test case, we consider this transfer function

\begin{align}
W(s)=\frac{s+0.1}{s+0.01}
\label{transferFunction1}
\end{align}

We define this transfer function as follows

tf1=signal.TransferFunction([1,0.1],[1,0.01])

where the first list contains the coefficients of the polynomial in the numerator and the second one, the coefficients of the polynomial in the denominator. The next step is to define the frequency points for generating the Bode plot. To do that, we need to define an array of frequency points in the logarithmic scale which are equally distributed in this scale. We do it like this

# start freq exponent

sFE=-4  # 10^-4

# end freq exponent

eFE=1 # 10^1

N=100

w=np.logspace(sFE,eFE,num=N,base=10)

First, we define the exponent of the start and end frequencies. The base is $10$. Next, we define the total number of points (in our case, 100). Then, we use the function np.logspace() to create an array of frequency points in the logarithmic scale.

Next, we use the function signa.bode(), to compute the log-magnitude and phase of the transfer function for the given transfer function and frequency points

wr, logMagnitude, phase =signal.bode(tf1,w)

The outputs of the function signal.bode are

  • the array of used frequency points
  • log-magnitude of the transfer function measured in decibels. The log magnitude is computed as follows: $20\log_{10} (M)$, where $M$ is the magnitude.
  • phase of the transfer function

The Bode plot consists of two graphs: the graph of the log magnitude function and the graph of the phase function.

We create the graph of the log-magnitude function as follows

plt.figure(figsize=(12,5))
plt.semilogx(wr, logMagnitude, color='blue',linewidth=3,linestyle='-')
plt.xlabel('frequency [rad/s]',fontsize=14)
plt.ylabel('Magnitude [dB]',fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=14)
plt.grid()
plt.savefig('magnitude.png',dpi=600)

We create the graph of the phase function as follows

plt.figure(figsize=(12,5))
plt.semilogx(wr, phase, color='blue',linewidth=3,linestyle='-')
plt.xlabel('frequency [rad/s]',fontsize=14)
plt.ylabel('Phase [deg]',fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=14)
plt.grid()
plt.savefig('phase.png',dpi=600)

We can place the graphs of the log magnitude and phase functions on the same figure as follows:

# define the subplot matrix and define the size
fig, ax = plt.subplots(2,1,figsize=(15,8))
ax[0].semilogx(wr,logMagnitude,color='blue',linestyle='-',linewidth=3)
#ax[0].set_xlabel("frequency [rad/s]",fontsize=14)
ax[0].set_ylabel("Magnitude [dB]",fontsize=14)
ax[0].tick_params(axis='both',labelsize=14)
ax[0].grid()
ax[1].semilogx(wr,phase, color='black', linestyle= '-',linewidth=3)
ax[1].set_xlabel("frequency [rad/s]",fontsize=14)
ax[1].set_ylabel("Phase [deg]",fontsize=14)
ax[1].tick_params(axis='both',labelsize=14)
ax[1].grid()
fig.savefig('complete.png',dpi=600)