from sympy import *
import numpy as np
from tabulate import tabulate
import pandas as pd
from scipy import signal
import matplotlib.pyplot as plt
import SymMNA
from IPython.display import display, Markdown, Math, Latex
init_printing()
31 SIDO-low-pass-filter
31.1 Introduction
The circuit shown in Figure 31.1, is from an electronics stackexchange question, where user349644 (2023) asks for help in determining the transfer function of the circuit. The circuit appears to be a low pass filter for the differential input to a following stage. I’m calling this circuit a Single-In-Dual-Out (SIDO) filter, where the output is the voltage across C3. An interesting response to the question was provided by Franc (2023), who has provided some interesting solutions to other circuit analysis questions. Usually his solutions are in the from of an image captured from a MathCAD type program consisting of equations, diagrams and plots.
31.2 Circuit description
The circuit has nine branches, five nodes and was interned into LTSpice in order to capture the schematic and generate the netlist.
31.3 Circuit analysis
The following analysis was performed.
- solve network equations for the transfer function
- pole, zero plot
- frequency response plot
- step and impusle response
- group delay
The net list for the filter is:
V1 1 0 1
C1 1 0 2.2n
C2 4 5 330p
C3 2 3 100p
R1 1 0 100k
R2 4 1 4.7k
R3 5 0 4.7k
R4 2 4 10k
R5 3 5 10k
The following Python modules are used.
31.3.1 Load the netlist
The netlist generated by LTSpice is pasted into the cell below and some edits were made to remove the inductor series resistance and the independent sources are set to their DC values.
= '''
net_list V1 1 0 1
C1 1 0 2.2e-9
C2 4 5 330e-12
C3 2 3 100e-12
R1 1 0 100e3
R2 4 1 4.7e3
R3 5 0 4.7e3
R4 2 4 10e3
R5 3 5 10e3
'''
Generate the network equations.
= SymMNA.smna(net_list)
report, network_df, df2, A, X, Z
# Put matricies into SymPy
= Matrix(X)
X = Matrix(Z)
Z
= Eq(A*X,Z) NE_sym
Generate markdown text to display the network equations.
= ''
temp for i in range(len(X)):
+= '${:s}$<br>'.format(latex(Eq((A*X)[i:i+1][0],Z[i])))
temp
Markdown(temp)
\(I_{V1} + v_{1} \left(C_{1} s + \frac{1}{R_{2}} + \frac{1}{R_{1}}\right) - \frac{v_{4}}{R_{2}} = 0\)
\(- C_{3} s v_{3} + v_{2} \left(C_{3} s + \frac{1}{R_{4}}\right) - \frac{v_{4}}{R_{4}} = 0\)
\(- C_{3} s v_{2} + v_{3} \left(C_{3} s + \frac{1}{R_{5}}\right) - \frac{v_{5}}{R_{5}} = 0\)
\(- C_{2} s v_{5} + v_{4} \left(C_{2} s + \frac{1}{R_{4}} + \frac{1}{R_{2}}\right) - \frac{v_{2}}{R_{4}} - \frac{v_{1}}{R_{2}} = 0\)
\(- C_{2} s v_{4} + v_{5} \left(C_{2} s + \frac{1}{R_{5}} + \frac{1}{R_{3}}\right) - \frac{v_{3}}{R_{5}} = 0\)
\(v_{1} = V_{1}\)
As shown above MNA generated many equations and these would be difficult to solve by hand and a symbolic soultion would take a lot of computing time. The equations are displace in matrix notation.
NE_sym
\(\displaystyle \left[\begin{matrix}I_{V1} + v_{1} \left(C_{1} s + \frac{1}{R_{2}} + \frac{1}{R_{1}}\right) - \frac{v_{4}}{R_{2}}\\- C_{3} s v_{3} + v_{2} \left(C_{3} s + \frac{1}{R_{4}}\right) - \frac{v_{4}}{R_{4}}\\- C_{3} s v_{2} + v_{3} \left(C_{3} s + \frac{1}{R_{5}}\right) - \frac{v_{5}}{R_{5}}\\- C_{2} s v_{5} + v_{4} \left(C_{2} s + \frac{1}{R_{4}} + \frac{1}{R_{2}}\right) - \frac{v_{2}}{R_{4}} - \frac{v_{1}}{R_{2}}\\- C_{2} s v_{4} + v_{5} \left(C_{2} s + \frac{1}{R_{5}} + \frac{1}{R_{3}}\right) - \frac{v_{3}}{R_{5}}\\v_{1}\end{matrix}\right] = \left[\begin{matrix}0\\0\\0\\0\\0\\V_{1}\end{matrix}\right]\)
The symbols generated by the Python code are extraced by the SymPy function free_symbols and then declared as SymPy variables.
# turn the free symbols into SymPy variables
str(NE_sym.free_symbols).replace('{','').replace('}','')) var(
\(\displaystyle \left( R_{3}, \ s, \ C_{2}, \ C_{1}, \ R_{5}, \ R_{2}, \ v_{2}, \ v_{5}, \ V_{1}, \ R_{1}, \ v_{1}, \ C_{3}, \ v_{3}, \ v_{4}, \ R_{4}, \ I_{V1}\right)\)
Built a dictionary of element values.
= SymMNA.get_part_values(network_df)
element_values element_values
\(\displaystyle \left\{ C_{1} : 2.2 \cdot 10^{-9}, \ C_{2} : 3.3 \cdot 10^{-10}, \ C_{3} : 1.0 \cdot 10^{-10}, \ R_{1} : 100000.0, \ R_{2} : 4700.0, \ R_{3} : 4700.0, \ R_{4} : 10000.0, \ R_{5} : 10000.0, \ V_{1} : 1.0\right\}\)
Generate the symbolic solution
= solve(NE_sym,X) U_sym
Display the symbolic solution
= ''
temp for i in U_sym.keys():
+= '${:s} = {:s}$<br>'.format(latex(i),latex(U_sym[i]))
temp
Markdown(temp)
\(v_{1} = V_{1}\)
\(v_{2} = \frac{C_{2} C_{3} R_{3} R_{4} V_{1} s^{2} + C_{2} C_{3} R_{3} R_{5} V_{1} s^{2} + C_{2} R_{3} V_{1} s + C_{3} R_{3} V_{1} s + C_{3} R_{5} V_{1} s + V_{1}}{C_{2} C_{3} R_{2} R_{4} s^{2} + C_{2} C_{3} R_{2} R_{5} s^{2} + C_{2} C_{3} R_{3} R_{4} s^{2} + C_{2} C_{3} R_{3} R_{5} s^{2} + C_{2} R_{2} s + C_{2} R_{3} s + C_{3} R_{2} s + C_{3} R_{3} s + C_{3} R_{4} s + C_{3} R_{5} s + 1}\)
\(v_{3} = \frac{C_{2} C_{3} R_{3} R_{4} V_{1} s^{2} + C_{2} C_{3} R_{3} R_{5} V_{1} s^{2} + C_{2} R_{3} V_{1} s + C_{3} R_{3} V_{1} s + C_{3} R_{5} V_{1} s}{C_{2} C_{3} R_{2} R_{4} s^{2} + C_{2} C_{3} R_{2} R_{5} s^{2} + C_{2} C_{3} R_{3} R_{4} s^{2} + C_{2} C_{3} R_{3} R_{5} s^{2} + C_{2} R_{2} s + C_{2} R_{3} s + C_{3} R_{2} s + C_{3} R_{3} s + C_{3} R_{4} s + C_{3} R_{5} s + 1}\)
\(v_{4} = \frac{C_{2} C_{3} R_{3} R_{4} V_{1} s^{2} + C_{2} C_{3} R_{3} R_{5} V_{1} s^{2} + C_{2} R_{3} V_{1} s + C_{3} R_{3} V_{1} s + C_{3} R_{4} V_{1} s + C_{3} R_{5} V_{1} s + V_{1}}{C_{2} C_{3} R_{2} R_{4} s^{2} + C_{2} C_{3} R_{2} R_{5} s^{2} + C_{2} C_{3} R_{3} R_{4} s^{2} + C_{2} C_{3} R_{3} R_{5} s^{2} + C_{2} R_{2} s + C_{2} R_{3} s + C_{3} R_{2} s + C_{3} R_{3} s + C_{3} R_{4} s + C_{3} R_{5} s + 1}\)
\(v_{5} = \frac{C_{2} C_{3} R_{3} R_{4} V_{1} s^{2} + C_{2} C_{3} R_{3} R_{5} V_{1} s^{2} + C_{2} R_{3} V_{1} s + C_{3} R_{3} V_{1} s}{C_{2} C_{3} R_{2} R_{4} s^{2} + C_{2} C_{3} R_{2} R_{5} s^{2} + C_{2} C_{3} R_{3} R_{4} s^{2} + C_{2} C_{3} R_{3} R_{5} s^{2} + C_{2} R_{2} s + C_{2} R_{3} s + C_{3} R_{2} s + C_{3} R_{3} s + C_{3} R_{4} s + C_{3} R_{5} s + 1}\)
\(I_{V1} = \frac{- C_{1} C_{2} C_{3} R_{1} R_{2} R_{4} V_{1} s^{3} - C_{1} C_{2} C_{3} R_{1} R_{2} R_{5} V_{1} s^{3} - C_{1} C_{2} C_{3} R_{1} R_{3} R_{4} V_{1} s^{3} - C_{1} C_{2} C_{3} R_{1} R_{3} R_{5} V_{1} s^{3} - C_{1} C_{2} R_{1} R_{2} V_{1} s^{2} - C_{1} C_{2} R_{1} R_{3} V_{1} s^{2} - C_{1} C_{3} R_{1} R_{2} V_{1} s^{2} - C_{1} C_{3} R_{1} R_{3} V_{1} s^{2} - C_{1} C_{3} R_{1} R_{4} V_{1} s^{2} - C_{1} C_{3} R_{1} R_{5} V_{1} s^{2} - C_{1} R_{1} V_{1} s - C_{2} C_{3} R_{1} R_{4} V_{1} s^{2} - C_{2} C_{3} R_{1} R_{5} V_{1} s^{2} - C_{2} C_{3} R_{2} R_{4} V_{1} s^{2} - C_{2} C_{3} R_{2} R_{5} V_{1} s^{2} - C_{2} C_{3} R_{3} R_{4} V_{1} s^{2} - C_{2} C_{3} R_{3} R_{5} V_{1} s^{2} - C_{2} R_{1} V_{1} s - C_{2} R_{2} V_{1} s - C_{2} R_{3} V_{1} s - C_{3} R_{1} V_{1} s - C_{3} R_{2} V_{1} s - C_{3} R_{3} V_{1} s - C_{3} R_{4} V_{1} s - C_{3} R_{5} V_{1} s - V_{1}}{C_{2} C_{3} R_{1} R_{2} R_{4} s^{2} + C_{2} C_{3} R_{1} R_{2} R_{5} s^{2} + C_{2} C_{3} R_{1} R_{3} R_{4} s^{2} + C_{2} C_{3} R_{1} R_{3} R_{5} s^{2} + C_{2} R_{1} R_{2} s + C_{2} R_{1} R_{3} s + C_{3} R_{1} R_{2} s + C_{3} R_{1} R_{3} s + C_{3} R_{1} R_{4} s + C_{3} R_{1} R_{5} s + R_{1}}\)
Transfer function
= ((U_sym[v2]-U_sym[v3])/U_sym[v1]).nsimplify().simplify().expand().together()
H_sym H_sym
\(\displaystyle \frac{1}{C_{2} C_{3} R_{2} R_{4} s^{2} + C_{2} C_{3} R_{2} R_{5} s^{2} + C_{2} C_{3} R_{3} R_{4} s^{2} + C_{2} C_{3} R_{3} R_{5} s^{2} + C_{2} R_{2} s + C_{2} R_{3} s + C_{3} R_{2} s + C_{3} R_{3} s + C_{3} R_{4} s + C_{3} R_{5} s + 1}\)
cancel(H_sym,s)
\(\displaystyle \frac{1}{s^{2} \left(C_{2} C_{3} R_{2} R_{4} + C_{2} C_{3} R_{2} R_{5} + C_{2} C_{3} R_{3} R_{4} + C_{2} C_{3} R_{3} R_{5}\right) + s \left(C_{2} R_{2} + C_{2} R_{3} + C_{3} R_{2} + C_{3} R_{3} + C_{3} R_{4} + C_{3} R_{5}\right) + 1}\)
= fraction(H_sym) #returns numerator and denominator H_sym_num, H_sym_denom
solve(H_sym_denom,s)
\(\displaystyle \left[ \frac{- C_{2} R_{2} - C_{2} R_{3} - C_{3} R_{2} - C_{3} R_{3} - C_{3} R_{4} - C_{3} R_{5} - \sqrt{C_{2}^{2} R_{2}^{2} + 2 C_{2}^{2} R_{2} R_{3} + C_{2}^{2} R_{3}^{2} + 2 C_{2} C_{3} R_{2}^{2} + 4 C_{2} C_{3} R_{2} R_{3} - 2 C_{2} C_{3} R_{2} R_{4} - 2 C_{2} C_{3} R_{2} R_{5} + 2 C_{2} C_{3} R_{3}^{2} - 2 C_{2} C_{3} R_{3} R_{4} - 2 C_{2} C_{3} R_{3} R_{5} + C_{3}^{2} R_{2}^{2} + 2 C_{3}^{2} R_{2} R_{3} + 2 C_{3}^{2} R_{2} R_{4} + 2 C_{3}^{2} R_{2} R_{5} + C_{3}^{2} R_{3}^{2} + 2 C_{3}^{2} R_{3} R_{4} + 2 C_{3}^{2} R_{3} R_{5} + C_{3}^{2} R_{4}^{2} + 2 C_{3}^{2} R_{4} R_{5} + C_{3}^{2} R_{5}^{2}}}{2 C_{2} C_{3} \left(R_{2} R_{4} + R_{2} R_{5} + R_{3} R_{4} + R_{3} R_{5}\right)}, \ \frac{- C_{2} R_{2} - C_{2} R_{3} - C_{3} R_{2} - C_{3} R_{3} - C_{3} R_{4} - C_{3} R_{5} + \sqrt{C_{2}^{2} R_{2}^{2} + 2 C_{2}^{2} R_{2} R_{3} + C_{2}^{2} R_{3}^{2} + 2 C_{2} C_{3} R_{2}^{2} + 4 C_{2} C_{3} R_{2} R_{3} - 2 C_{2} C_{3} R_{2} R_{4} - 2 C_{2} C_{3} R_{2} R_{5} + 2 C_{2} C_{3} R_{3}^{2} - 2 C_{2} C_{3} R_{3} R_{4} - 2 C_{2} C_{3} R_{3} R_{5} + C_{3}^{2} R_{2}^{2} + 2 C_{3}^{2} R_{2} R_{3} + 2 C_{3}^{2} R_{2} R_{4} + 2 C_{3}^{2} R_{2} R_{5} + C_{3}^{2} R_{3}^{2} + 2 C_{3}^{2} R_{3} R_{4} + 2 C_{3}^{2} R_{3} R_{5} + C_{3}^{2} R_{4}^{2} + 2 C_{3}^{2} R_{4} R_{5} + C_{3}^{2} R_{5}^{2}}}{2 C_{2} C_{3} \left(R_{2} R_{4} + R_{2} R_{5} + R_{3} R_{4} + R_{3} R_{5}\right)}\right]\)
31.3.2 Numerical solution
= NE_sym.subs(element_values)
NE NE
\(\displaystyle \left[\begin{matrix}I_{V1} + v_{1} \cdot \left(2.2 \cdot 10^{-9} s + 0.000222765957446809\right) - 0.000212765957446809 v_{4}\\- 1.0 \cdot 10^{-10} s v_{3} + v_{2} \cdot \left(1.0 \cdot 10^{-10} s + 0.0001\right) - 0.0001 v_{4}\\- 1.0 \cdot 10^{-10} s v_{2} + v_{3} \cdot \left(1.0 \cdot 10^{-10} s + 0.0001\right) - 0.0001 v_{5}\\- 3.3 \cdot 10^{-10} s v_{5} - 0.000212765957446809 v_{1} - 0.0001 v_{2} + v_{4} \cdot \left(3.3 \cdot 10^{-10} s + 0.000312765957446809\right)\\- 3.3 \cdot 10^{-10} s v_{4} - 0.0001 v_{3} + v_{5} \cdot \left(3.3 \cdot 10^{-10} s + 0.000312765957446809\right)\\v_{1}\end{matrix}\right] = \left[\begin{matrix}0\\0\\0\\0\\0\\1.0\end{matrix}\right]\)
= solve(NE,X)
U U
\(\displaystyle \left\{ I_{V1} : \frac{- 1.452 \cdot 10^{18} s^{3} - 1.49089787234043 \cdot 10^{24} s^{2} - 2.86214893617021 \cdot 10^{29} s - 1.06382978723394 \cdot 10^{33}}{6.6 \cdot 10^{26} s^{2} + 6.42765957446809 \cdot 10^{32} s + 1.06382978723404 \cdot 10^{38}}, \ v_{1} : 1.0, \ v_{2} : \frac{660000000.0 s^{2} + 642765957446809.0 s + 2.12765957446809 \cdot 10^{20}}{1320000000.0 s^{2} + 1.28553191489362 \cdot 10^{15} s + 2.12765957446809 \cdot 10^{20}}, \ v_{3} : \frac{660000000.0 s^{2} + 642765957446809.0 s}{1320000000.0 s^{2} + 1.28553191489362 \cdot 10^{15} s + 2.12765957446809 \cdot 10^{20}}, \ v_{4} : \frac{330000000.0 s^{2} + 427765957446809.0 s + 1.06382978723404 \cdot 10^{20}}{660000000.0 s^{2} + 642765957446809.0 s + 1.06382978723404 \cdot 10^{20}}, \ v_{5} : \frac{330000000.0 s^{2} + 215000000000000.0 s}{660000000.0 s^{2} + 642765957446809.0 s + 1.06382978723404 \cdot 10^{20}}\right\}\)
= ''
temp for i in U.keys():
+= '${:s} = {:s}$<br>'.format(latex(i),latex(U[i]))
temp
Markdown(temp)
\(v_{1} = 1.0\)
\(v_{2} = \frac{660000000.0 s^{2} + 642765957446809.0 s + 2.12765957446809 \cdot 10^{20}}{1320000000.0 s^{2} + 1.28553191489362 \cdot 10^{15} s + 2.12765957446809 \cdot 10^{20}}\)
\(v_{3} = \frac{660000000.0 s^{2} + 642765957446809.0 s}{1320000000.0 s^{2} + 1.28553191489362 \cdot 10^{15} s + 2.12765957446809 \cdot 10^{20}}\)
\(v_{4} = \frac{330000000.0 s^{2} + 427765957446809.0 s + 1.06382978723404 \cdot 10^{20}}{660000000.0 s^{2} + 642765957446809.0 s + 1.06382978723404 \cdot 10^{20}}\)
\(v_{5} = \frac{330000000.0 s^{2} + 215000000000000.0 s}{660000000.0 s^{2} + 642765957446809.0 s + 1.06382978723404 \cdot 10^{20}}\)
\(I_{V1} = \frac{- 1.452 \cdot 10^{18} s^{3} - 1.49089787234043 \cdot 10^{24} s^{2} - 2.86214893617021 \cdot 10^{29} s - 1.06382978723394 \cdot 10^{33}}{6.6 \cdot 10^{26} s^{2} + 6.42765957446809 \cdot 10^{32} s + 1.06382978723404 \cdot 10^{38}}\)
= ((U[v2]-U[v3])/U[v1]).nsimplify().simplify().expand().together()
H H
\(\displaystyle \frac{10638297872340450000}{66000000 s^{2} + 64276595744681 s + 10638297872340450000}\)
31.3.2.1 Convert transfer function to SciPy system
In this section we convert the SymPy equations into Numpy format.
Extract the numerator and denominator polynomials so that the system can be defined in SciPy.
= fraction(H) #returns numerator and denominator H_num, H_denom
The SciPy function, TransferFunction, represents the system as the continuous-time transfer function and takes as inputs the coeeficients of the numerator and denominator polynominals.
# convert symbolic to numpy polynomial
= np.array(Poly(H_num, s).all_coeffs(), dtype=float)
a = np.array(Poly(H_denom, s).all_coeffs(), dtype=float)
b = signal.TransferFunction(a,b) sys
31.3.3 Poles and zeros of the low pass transfer function
The poles and zeros of the transfer function can easly be obtained with the following code:
= np.roots(sys.num)
sys_zeros = np.roots(sys.den) sys_poles
31.3.3.1 Low pass filter pole zero plot
The poles and zeros of the preamp transfer function are plotted.
'ob', markerfacecolor='none')
plt.plot(np.real(sys_zeros), np.imag(sys_zeros), 'xr')
plt.plot(np.real(sys_poles), np.imag(sys_poles), 'Zeros', 'Poles'], loc=1)
plt.legend(['Pole / Zero Plot')
plt.title('real part, \u03B1')
plt.xlabel('imaginary part, j\u03C9')
plt.ylabel(
plt.grid() plt.show()
Poles and zeros of the transfer function plotted on the complex plane. The units are in radian frequency.
Printing these values in Hz.
print('number of zeros: {:d}'.format(len(sys_zeros)))
for i in sys_zeros:
print('{:,.2f} Hz'.format(i/(2*np.pi)))
number of zeros: 0
print('number of poles: {:d}'.format(len(sys_poles)))
for i in sys_poles:
print('{:,.2f} Hz'.format(i/(2*np.pi)))
number of poles: 2
-121,354.74 Hz
-33,644.32 Hz
31.3.4 Bode plot
Use the SciPy function bode to plot the magnitude and phase of the filter. In electrical engineering, a Bode plot is a graph of the frequency response of a system. It is usually a combination of the magnitude (usually in decibels) of the frequency response and the phase shift. As originally conceived by Hendrik Wade Bode in the 1930s, the plot is an asymptotic approximation of the frequency response, using straight line segments. Bode plots are used to assess the stability of systems by finding the gain and phase margins.
= np.logspace(3, 8, 200, endpoint=False)*2*np.pi
x = signal.bode(sys, w=x) # returns: rad/s, mag in dB, phase in deg
w, mag, phase
= plt.subplots()
fig, ax1 'magnitude, dB')
ax1.set_ylabel('frequency, Hz')
ax1.set_xlabel(
/(2*np.pi), mag,'-b') # Bode magnitude plot
plt.semilogx(w
='y')
ax1.tick_params(axis#ax1.set_ylim((-30,20))
plt.grid()
# instantiate a second y-axes that shares the same x-axis
= ax1.twinx()
ax2 = 'tab:blue'
color
/(2*np.pi), phase,':',color='tab:red') # Bode phase plot
plt.semilogx(w
'phase, deg',color=color)
ax2.set_ylabel(='y', labelcolor=color)
ax2.tick_params(axis#ax2.set_ylim((-5,25))
'Magnitude and phase response')
plt.title( plt.show()
31.3.5 Filter impulse and step response
Use the SciPy functions impulse2 and step2 to plot the impulse and step response of the system.
The impulse and step response of the filter are plotted below. Any linear, time-invariant is completely characterized by its impulse response. The transfer function is the Laplace transform of the impulse response. The impulse response defines the response of a linear time-invariant system for all frequencies.
In electronic engineering and control theory, step response is the time behavior of the outputs of a general system when its inputs change from zero to one in a very short time.
1,2,figsize=(15, 5))
plt.subplots(
# using subplot function and creating
# plot one
1, 2, 1)
plt.subplot(
# impulse response
= signal.impulse2(sys,N=500)
t, y /1e-3, y)
plt.plot(t'Impulse response')
plt.title('volts')
plt.ylabel('time, msec')
plt.xlabel(
plt.grid()
# using subplot function and creating plot two
1, 2, 2)
plt.subplot(
= signal.step2(sys,N=500)
t, y /1e-3, y)
plt.plot(t'Step response')
plt.title('volts')
plt.ylabel('time, msec')
plt.xlabel(
plt.grid()
# show plot
plt.show()
31.3.6 Low pass filter group delay
The following python code calculates and plots group delay. Frequency components of a signal are delayed when passed through a circuit and the signal delay will be different for the various frequencies unless the circuit has the property of being linear phase. The delay variation means that signals consisting of multiple frequency components will suffer distortion because these components are not delayed by the same amount of time at the output of the device.
Group delay: \(\tau _{g}(\omega )=-\frac {d\phi (\omega )}{d\omega }\)
#w_preamp, mag_preamp, phase_preamp = bp_sys.bode(w=x_axis_range)
'group delay')
plt.title(/(2*np.pi), -np.gradient(phase*np.pi/180)/np.gradient(w),'-',label='group delay')
plt.semilogx(w
'Group delay, sec')
plt.ylabel('Frequency, Hz')
plt.xlabel(
plt.legend()
plt.grid() plt.show()