Major libraries that have been used in this tutorial

In [5]:
import math
import sympy
import scipy
import control
import numpy as np
from scipy import signal
from matplotlib import pyplot as plt

Introduction to Signals and systems in python

Welcome to Signals and systems!

In this simple tutorial, we will learn about python3's basic commands and methods that we will use them for Signal processing, Dynamic systems and control theory.

Consider that this tutorial uses Python 3.7.0

Note

This tutorial has been created by $\href{https://hnxj.github.io}{Hamed \: Nejat}$ for Signals and systems course of $\href{https://ee.sharif.edu}{Electrical \: Engineering \: Department \: of \: Sharif \: University \: of \: Technology}$, with major help of official documents of Numpy, Scipy, Matplotlib, Control and Sympy. special thanks to all other python developers, this tutorial is free to use.

Anaconda is easy to use, hence pure python is also suitable for this course

Windows (x64/x86) (~490MB) : https://repo.anaconda.com/archive/Anaconda3-2019.07-Windows-x86_64.exe

for MacOS (~650MB) : https://repo.anaconda.com/archive/Anaconda3-2019.07-MacOSX-x86_64.pkg

for Linux (~510MB) : https://repo.anaconda.com/archive/Anaconda3-2019.07-Linux-x86_64.sh

Make sure that JupyterLab is installed by this command on Anaconda Prompt :

Virtual environments:

In anaconda prompt, write:

-->conda create -n env_name python=3.7

Then everytime you want to code for this course, write:

-->conda activate evn_name

And when you dont want to use this env:

-->conda deactivate env_name

for anaconda users:

-->conda install jupyter

for main python users:

-->pip install

Run Jupyter notebook:

Open Anaconda prompt, one of the ways is to search "Anaconda prompt" in windows search.

After opening of Anaconda Prompt, Enter:

-->jupyter lab

Then, you are in jupyter environment. click on file tab, then click on new notebook.

Run Spyder scientific IDE

On cmd/terminal, install spyder using this commands:

-->conda install spyder

To run:

-->spyder

Contact

For more questions about tutorial class or homeworks:

contact us : hamednejat7@gmail.com

0: Introduction to python

0.1: Basic commands

EXAMPLES: 

In the begining of almost every programming language, we try to print a text or a number. python is easy to 
start! here are some examples of printing variables:

Input/Output (I/O)

In [2]:
print("You will never walk alone") # Raw string
print("There is" + " a " + "GoldenSky :)" # Multi strings
    " Walk on", "trough the rain") # multi line
You will never walk alone
There is a GoldenSky :) Walk on trough the rain
In [3]:
a = int(input("Enter int number"))
b = float(input("enter float"))
s = str(input("Enter string"))

print(a, b, s)
12653451253 123.123 YNWA
In [6]:
print(6) # Integers
print(-7) # Integers
print(18.0) # Floats
print("18.0 " + str(-32.4353)) # Numbers to string
print(float("18.34") + 32.4353) # Strings to number
print(float("-18.34") + 32.4353) # Signed strings to number, Sign is included in
                                 # number while it was converted from string to number!
6
-7
18.0
18.0 -32.4353
50.7753
14.095299999999998
In [6]:
print("First number: {}, Second number: {} and so on ...".format(4.01, 3.98))

print ('%.2f'%(11.1725612)) # 2-digit precision, rounded down because 11.1725612 < 11.175
print ('%.2f'%(11.1775612)) # 2-digit presicion rounded up because 11.1775612 > 11.175

print('%.5f'%(float("18.34") + 32.4353)) # Strings to number, 5-digit precision
print('%.3f'%(float("-18.34") + 32.4353)) # Signed strings to number, 3-digit precision

print('%f %d %s'%(11.13, 11.13, 11.13)) # printing float, integer and string in same command.
print('%f'%(1323.12312312312)) # NOTE!: default float precision is 6-digits
First number: 4.01, Second number: 3.98 and so on ...
11.17
11.18
50.77530
14.095
11.130000 11 11.13
1323.123123

0.2: Some data type and variable assignment

0.2.1: integer, float, string, char

In [7]:
a = 4 # integer
b = -5.12 # float
c = "Estereeng" # string 

# char is almost same with str in python, except char array in classes
d = chr(113) # <C: char> = chr(<I: integer>) method returns the character that has ascii=I
e = ord('q') # <I: integer> = ord(<C: char>) method returns the ascii that char=C

print("\nType of a is :", type(a), "a :", a,
      "\nType of b is :", type(b), "b :", b,
      "\nType of c is :", type(c), "c :", c,
      "\nType of d is :", type(d), "d :", d,
      "\nType of e is :", type(e), "d :", e)
Type of a is : <class 'int'> a : 4 
Type of b is : <class 'float'> b : -5.12 
Type of c is : <class 'str'> c : Estereeng 
Type of d is : <class 'str'> d : q 
Type of e is : <class 'int'> d : 113

0.2.2: Dictionary

In [9]:
e = dict() # dict :: e[<Key>] = <Val>
e["asd"] = 12 # assigning num to string key
e[1] = a # integer to integer 
e[b] = 'L' # char (string) to float 

print("\nType of e is :", type(e), "\n e :", e)
print("e[\"asd\"] is : ", e["asd"])
Type of e is : <class 'dict'> 
 e : {'asd': 12, 1: 4, -5.12: 'L'}
e["asd"] is :  12

0.2.3: List

In [10]:
f = list() # alternate for list / f = [1, 1.1, list]
f.append(1) # appending an integer
f.append(1.1) # appending a float

f.append("paganini") # appending a string
f.append(e) # appending a dictionary!
print("\nType of e is :", type(f), "e :", f)

f.pop(2) # pop 2nd element
print("\nType of e is :", type(f), "e :", f)
f.pop() # pop last element
print("\nType of e is :", type(f), "e :", f)
Type of e is : <class 'list'> e : [1, 1.1, 'paganini', {'asd': 12, 1: 4, -5.12: 'L'}]

Type of e is : <class 'list'> e : [1, 1.1, {'asd': 12, 1: 4, -5.12: 'L'}]

Type of e is : <class 'list'> e : [1, 1.1]

0.2.4: Set

In [12]:
f = set() # alternate for list / f = [1, 1.1, list]
f.add(1) # add an integer to the set
f.add(1.1) # appending a float
f.add(2)
f.add(-1)

f.add("vivaldi") # appending a string
f.add("antonio")
# f.add(e) # appending a dictionary!
print("\nType of e is :", type(f), "e :", f)

f.remove("antonio") # remove one existing element

try: # prevents unexcepted errors and interrupts in program 
    f.remove("bach") # key is not in the set
except KeyError: 
    print("\nKey is not in the set")
except:
    print("Unknown error")
    
print("\nType of e is :", type(f), "e :", f)
f.pop() # pop last element
print("\nType of e is :", type(f), "e :", f)
Type of e is : <class 'set'> e : {'vivaldi', 1, 2, 1.1, 'antonio', -1}

Key is not in the set

Type of e is : <class 'set'> e : {'vivaldi', 1, 2, 1.1, -1}

Type of e is : <class 'set'> e : {1, 2, 1.1, -1}

0.3: Computation

In this subsection, we will do some maths computations and evaluate errors.

In [82]:
import math
import numpy as np

a = 12.250
b = 27.601
c = a * b
d = a / b

print(a, b, c, d)
12.25 27.601 338.11225 0.443824499112351
In [15]:
a = 3.131
b = 13.313
c = a ** b # a power b
d = np.sqrt(b) # radical b

print(a, b, c, d)
3.131 13.313 3972177.79380536 3.6486983980592314
In [16]:
a = 3.131
b = np.sin(a) # numpy sinus; is radian based
c = np.cos(a) # numpy cosinus; is radian based
d = np.exp(c) # exponential

print(a, b, c, d)
3.131 0.010592455500673467 -0.9999438983695367 0.3679000803868474
In [17]:
a = -3.131 + 4.023j # complex number
b = np.sin(a) # numpy sinus; is radian based
c = np.cos(a) # numpy cosinus; is radian based
d = np.exp(c) # exponential

print(a, b, c, d)
(-3.131+4.023j) (-0.29598688665547973-27.92371377790813j) (-27.941611960486863+0.2957972903163078j) (7.011789697864798e-13+2.1367541336939334e-13j)

0.4: Loop and Condition

In [17]:
import numpy as np
first = -4
last = 6
step = 2
cnt = 0.5

np_array1 = np.array([1, 0.7, -4, 2.0 + 2.0j, -0.5j])
np_array2 = np.array([[1, 2], [0.5j, -2], [4.0, 0.3j]])

0.4.1: For loops

In [5]:
for i in range(first, last, step): # ranged loop
    print(i**2.5)

for j in np_array1: # structured loop
    print(j)

for i, j in np_array2: # structured double loop
    print(i, j)
    
In [9]:
for i in range(7):
    print(i)
0
1
2
3
4
5
6

0.4.2: While loops

In [18]:
while cnt < 10:
    cnt *= 2.7
    print(cnt)
1.35
3.6450000000000005
9.841500000000002
26.572050000000008

0.4.3: If conditions

In [10]:
if step > 10:
    pass # the command that does nothing!
    # the code
elif step == 10: # Else If equivalent
    pass
    # the code
elif step == 9:
    quit() # Exit from the code or program !
else: # otherwise
    print("Nothing else matters")
Nothing else matters

0.5: Functions

In [32]:
import numpy as np

# Basic recursive function
def gcd(a, b): # Greatest Common Divisor (GCD)
    if b > a:
        (a, b) (b, a) # nice swap in Python!
    if b == 0:
        return a
    return gcd(b, a%b)


R = 14
a = -1
u = lambda x : (x + abs(x))/2
f = lambda x : u(x) * (np.exp(3*x) + 3)  


print(gcd(2436, 504))
print(f(1.2 + 1.0j), f(-1 - 0.01j), u(1), u(0.0), u(-1))

# IMPORTANT NOTE: Python has recursion limit, default recursion limit is 1000.
# you can change it by setrecursionlimit(<New Limit>)
import sys
sys.setrecursionlimit(10000)
84
(-48.47656220326329-9.483349516957443j) (6.877527054236574e-05-0.015248860663103969j) 1.0 0.0 0.0
In [33]:
# Void function
def say_hello():
    print("Hello World :]")
    return # void return


say_hello()
Hello World :]
In [16]:
# lambda function
hemographic1 = lambda x : (2 * x + 4) / (3 * x - 1)
print(hemographic1(4))
1.0909090909090908

1: Statistic and probabilistic computation

1.1: Random distributions and numbers

np.random is numpy's random variables and distributions sub-library.

In [24]:
a = np.random.rand(5) # 10 random numbers uniformly between 0-1
b = np.random.randn(1000) # 1000 Gaussian standard distributed random numbers with mean = 0, std = 1
c = np.random.randint(-3, 14, 10) # 10 random integers between -3 and 13
d = np.random.gamma(2.5, 1, [2, 2]) # beta, gumbel, ... other distributions are available

print(a)
print(np.mean(b))
print(np.var(b))
print(c)
print(d)
[0.72043503 0.42271848 0.34518329 0.52325653 0.95223927]
0.0489575868952785
0.9714511651099623
[13  5  2  5  7  8  9 -1 -1  9]
[[0.52462323 1.44135035]
 [3.56640445 7.56055784]]

1.2: Stats and statistic variables

numpy has built-in important statistic functions, In the plotting part we will show simple statistical plotting

In [26]:
import numpy as np

interval_first = 0
interval_last = 10
step = 2
amount = 33

e = np.arange(interval_first, interval_last, step) # equivalent to matlab's i:j with step 
f = np.linspace(interval_first, interval_last, amount) # Splits line space between 1 and 10 into 25 numbers
g = np.mean(e)
h = np.var(f)

i = np.std(f)
j = np.max(e)
k = np.min(f)
l = np.median(e)

print(e)
print(f)
print(len(e)) # returns length of structure
print(len(f)) # returns length of structure

print(g, h, i)
print(j, k, l)
[0 2 4 6 8]
[ 0.      0.3125  0.625   0.9375  1.25    1.5625  1.875   2.1875  2.5
  2.8125  3.125   3.4375  3.75    4.0625  4.375   4.6875  5.      5.3125
  5.625   5.9375  6.25    6.5625  6.875   7.1875  7.5     7.8125  8.125
  8.4375  8.75    9.0625  9.375   9.6875 10.    ]
5
33
4.0 8.854166666666666 2.9755951785595207
8 0.0 4.0

2: Visualization and plotting with matplotlib

2.1: Simple plotting

Here we see an example of a plot.

Initialize arrays: and plot them:

In [81]:
from matplotlib import pyplot as plt

x = np.linspace(-3, 3, 100)
y1 = np.exp(x)
y2 = np.cos(x)
y3 = np.random.randn(100)

p1 = plt.plot(x, y3, label='test', color='green', marker='o', linestyle='dashed',
         linewidth=2, markersize=2)

p1[0].set_label('test')
plt.xlabel("X")
plt.ylabel("Y")
plt.title("Test")

plt.legend(handles=p1, labels='G', loc='lower right')
plt.grid(1)
plt.axis('on')
plt.show()

# print(dir(plt))

2.2: Figure object and plotting properties

In [8]:
fig, ax = plt.subplots(nrows=2, ncols=2) # number of subplots
fig.set_figwidth(19) # figure width in cm
fig.set_figheight(11) # figure height in cm


ax[0, 0].plot(x, y2, label='Cos', color='red') # plotting with specs
ax[0, 0].plot(x, y3, label='Rnd') # colors, scatters and ... 
ax[0, 0].plot(x, y1, label='Sin')
ax[0, 1].scatter(x, y2, label='Cos', color='cyan')
ax[1, 0].plot(x, y3, label='Rnd')
ax[1, 1].plot(x, y2, label='Cos')
ax[1, 1].plot(x, y3, label='Rnd')


ax[0, 1].annotate('Cos', (1, np.cos(1)),
            xytext=(0.8, 0.9), textcoords='axes fraction',
            arrowprops=dict(facecolor='black', shrink=0.05),
            fontsize=16,
            horizontalalignment='right', verticalalignment='top') # annotation cursor


ax[0, 0].set_title("The title !")
ax[0, 0].set_xlabel("X description")
ax[0, 0].set_ylabel("Y description")

ax[1, 0].set_title("The title !")
ax[1, 0].set_xlabel("X description")
ax[1, 0].set_ylabel("Y description")

ax[0, 1].set_title("The scatter")
ax[0, 1].set_xlabel("X description")
ax[0, 1].set_ylabel("Y description")

ax[1, 1].grid('on')
ax[1, 1].axis('off')

ax[0, 0].legend()
fig.show()
C:\Anaconda\lib\site-packages\ipykernel_launcher.py:38: UserWarning: Matplotlib is currently using module://ipykernel.pylab.backend_inline, which is a non-GUI backend, so cannot show the figure.

3: Introduction to object-oriented python

3.1: Classes

In this course, we usually use object-oriented programming tools.

here, we have an example of two classes with inheritance, static method and submethods, keywords in function inputs.

In [54]:
from matplotlib import pyplot as plt
import numpy

class plotter:
    
    def __init__(self, rows, cols, width, height):
        self.fig, self.ax = plt.subplots(nrows=rows, ncols=cols)
        self.fig.set_figwidth(width)
        self.fig.set_figwidth(height)
        return
    
    @staticmethod
    def plot(ax, x, y, title="", x_label="", y_label="", grid='on', axis='on'):
        ax.plot(x, y)
        ax.set_title(title)
        ax.set_xlabel(x_label)
        ax.set_ylabel(y_label)
        
        ax.grid(grid)
        ax.axis(axis)
        return

class painter(plotter): # inheritance from plotter class
    
    def __init__(self, boom_width, boom_height, width=21, height=12):
        super().__init__(rows=1, cols=1, width=width, height=height)
        self.boom_width = boom_width
        self.boom_height = boom_height
        return
    
    def sinusy(self, x):
        self.ax.plot(x, np.sin(x))
        return
        
    def exponenty(self, x):
        self.ax.plot(x, np.exp(x))
        return
    
    def free_draw(self, x, y):
        self.ax.plot(x, y)
        print("The rawing")
        return
In [55]:
msr = painter(100, 100)
msr.sinusy(x=np.linspace(1, 19, 50))
msr.exponenty(x=np.linspace(-2, 2, 60))

msr.plot(ax=msr.ax, x=np.random.rand(30), y=np.linspace(10, 20, 30), title="Drawing")
msr.plot(ax=msr.ax, x=np.random.randint(5, 10, 50), y=np.linspace(2, 18, 50), title="Drawing")
msr.plot(ax=msr.ax, x=np.linspace(2, 18, 50), y=np.random.randint(1, 20, 50), title="Drawing")

4. Signals

4.1: Definition and plotting signals

Example : Define two signals and plot them

In [122]:
import numpy as np
import matplotlib.pyplot as plt

u = lambda x : (np.sign(x) + 1)/2
f1 = lambda x : (np.exp(-x) * np.sin(10*x)) * u(t)
f2 = lambda x : (np.exp(-2*x) * np.cos(5*x)) * u(t)

t = np.linspace(-2, 6, 1000)
fig, ax = plt.subplots(1, 1)
fig.set_figwidth(19)
fig.set_figheight(11)

p1 = ax.plot(t, f1(t))
p2 = ax.plot(t, f2(t))
p1[0].set_label('F1')
p2[0].set_label('F2')

ax.legend(loc='upper right')
ax.set_xlabel("tau")
ax.set_ylabel("expression value")
ax.grid(True)

ax.set_ylim(ymin=-1, ymax=1)
ax.set_xlim(xmin=-1, xmax=5)
fig.show()
c:\anaconda\envs\signal\lib\site-packages\ipykernel_launcher.py:26: UserWarning: Matplotlib is currently using module://ipykernel.pylab.backend_inline, which is a non-GUI backend, so cannot show the figure.
In [128]:
from matplotlib.pyplot import *
import numpy as np

x = np.linspace(0, 10, 1000)
y = np.sin(x**2)

plot(x, y)
axis('on')
grid('on')
show()

4.2: Signal computations

Integration, derivation

4.2.1: Derivative

example: symbolic derivation :

$$ f_{(t)} = e^{-t} + sin(2t) + t^{3}sin(3t) $$

$$ f^{\prime}_{(t)} = \dfrac{\partial f_{(t)}}{\partial t} = diff(f, t, 1)$$

In [166]:
from sympy import * # imports all required functions, methods and types

x, y, z, t = symbols('x y z t') # symbols

f1 = cos(-t) + sin(2*t) + t**3 * exp(-3*t)
fd1 = diff(f1, t, 1) # First order derivative 

print("\n First function:")
print(fd1)
print(fd1.subs(t, 3)) # string output
print(fd1.subs(t, 27.76275)) # float output

f2 = sin(x + y) + x*y**3 + x*y**4
fd2 = diff(diff(f2, y, 1), x, 1) # second-order derivative based on y
print("\n Second function:")
print(fd2)
print(fd2.subs(x, 2.).subs(y, 1.)) # getting values from the function
 First function:
-3*t**3*exp(-3*t) + 3*t**2*exp(-3*t) - sin(t) + 2*cos(2*t)
-sin(3) - 54*exp(-9) + 2*cos(6)
0.551769145867537

 Second function:
4*y**3 + 3*y**2 - sin(x + y)
6.85887999194013

4.2.2: Integration

example: symbolic integration :

$$ f_{(t)} = te^{-3t} + cos(2t) + t^{2}cos(t) $$

$$ \int^{t}_{-\infty} f_{(t)} d t = integrate(f, t) $$

In [171]:
from sympy import * # imports all required functions, methods and types

x, y, z, t = symbols('x y z t') # symbols

f1 = cos(-t) + sin(2*t) + t**3 * exp(-3*t)
fd1 = integrate(f1, t) # First order derivative 

print("\n First function:")
print(fd1)
print(fd1.subs(t, 3)) # string output
print(fd1.subs(t, 27.76275)) # float output

f2 = sin(x + y) + x*y**3 + x*y**4
fd2 = integrate(integrate(f2, y), x) # second-order derivative based on y
print("\n Second function:")
print(fd2)
print(fd2.subs(x, 2.).subs(y, 1.)) # getting values from the function
 First function:
(-9*t**3 - 9*t**2 - 6*t - 2)*exp(-3*t)/27 + sin(t) - cos(2*t)/2
-cos(6)/2 - 344*exp(-9)/27 + sin(3)
0.229226932445039

 Second function:
x**2*y**5/10 + x**2*y**4/8 - sin(x + y)
0.758879991940133

4.2.3: Convolution

example: symbolic convolution :

This is very slow method for convolution. later we will introduce a better method

In [4]:
from sympy import *


def convolve(f, g, t):
    tau = Symbol('tau')
    return integrate(f.subs(t, tau) * g.subs(t, t - tau), tau)


t = Symbol('t')
f1 = exp(-t) * Heaviside(t)
f2 = Heaviside(t)
f3 = convolve(f1, f2, t)

print(f3)
(-(1 - exp(-t))*Heaviside(t) + (1 - exp(-tau))*Heaviside(tau))*Heaviside(t - tau)

4.2.4: Numerical differentiation

In [84]:
import numpy as np

x = np.random.randint(-3, 3, 10)
y1 = np.gradient(x, 1) # the interpolated gradient
y2 = x[0:len(x)-1] - x[1:len(x)] # normal gradient

print(x)
print(y1)
print(y2)
[-2  1 -3  2  0  2 -2 -3  0  2]
[ 3.  -0.5  0.5  1.5  0.  -1.  -2.5  1.   2.5  2. ]
[-3  4 -5  2 -2  4  1 -3 -2]

4.3: Integral Transforms

4.3.1 Laplace transform

In [13]:
from sympy.integrals import laplace_transform
from sympy.abc import t, s, a

F1 = t**2 + sin(t) + exp(3*t)
F2 = t + cos(t)
L1 = laplace_transform(F1, t, s)
L2 = laplace_transform(F2, t, s)
L3 = L1[0]*L2[0]

print(L1, L2, L3)
(1/(s**2 + 1) + 1/(3*(s/3 - 1)) + 2/s**3, 3, Ne(s/3, 1)) ((s**3 + s**2 + 1)/(s**4 + s**2), 0, True) (s**3 + s**2 + 1)*(1/(s**2 + 1) + 1/(3*(s/3 - 1)) + 2/s**3)/(s**4 + s**2)

4.3.2: Inverse Laplace transform

In [30]:
from sympy.integrals.transforms import inverse_laplace_transform
from sympy import exp, Symbol
from sympy.abc import s, t
a = Symbol('a', positive=True)

# F3 = inverse_laplace_transform(L3, s, t)
L4 = exp(-a*s)/s**2
F4 = inverse_laplace_transform(L4, s, t)

print(F4)
(-a + t)*Heaviside(-a + t)

4.3.3: Mellin transform

In [106]:
from sympy.integrals.transforms import mellin_transform
from sympy import exp
from sympy.abc import x, s

F1 = exp(-x) + sin(x)
M1 = mellin_transform(F1, x, s)
print(M1)
((2**s*sqrt(pi)*gamma(s/2 + 1/2) - gamma(-s/2)*gamma(s + 1))/(2*gamma(1 - s/2)), (0, 1), True)

4.3.4: Fourier and I-Fourier transform

In [19]:
from sympy import fourier_transform, exp, inverse_fourier_transform
from sympy.abc import x, k

F1 = exp(-x*2) * Heaviside(x)
Q1 = fourier_transform(F1, x, k)
Q2 = fourier_transform(F2, x, k, noconds=False)
F2 = inverse_fourier_transform(Q1, k, x)

print(Q1)
print(Q2)
print(F2.simplify())
1/(2*(I*pi*k + 1))
FourierTransform(InverseFourierTransform(1/(I*pi*k + 1), k, x), x, k)/2
InverseFourierTransform(1/(I*pi*k + 1), k, x)/2

5. Differential equations

5.1 Definition

In python-sympy, to start a DE session, at first we need to assign differential equations.

Look at this simple example:

In [31]:
from sympy import Function, dsolve, Eq, Derivative, sin, cos, symbols
from sympy.abc import x

f = Function('f')(x) # Defining F as function of x
fp = Derivative(f, x) # F prime (1st derivative)
fz = Derivative(f, x, x) # F zegond (2nd derivative)
fy = Derivative(fz, x)

deq1 = fz + 9*f
p1 = dsolve(deq1, f)

print(p1)
Eq(f(x), C1*sin(3*x) + C2*cos(3*x))

6. Discrete signals

In [ ]:
 

7. State-space equations

8. Transfer functions

9. LTI system

10. Filters

11. Block diagrams

12. Control systems

End