#import os
from sympy import *
import numpy as np
from tabulate import tabulate
from scipy import signal
import matplotlib.pyplot as plt
import pandas as pd
import SymMNA
from IPython.display import display, Markdown, Math, Latex
init_printing()
7 Test 3
The circuit in Figure 7.1 is from Johnson, Hilburn, and Johnson (1978) (Figure 4.8), but modified to include a capactior and inductor. AC analysis was performed at 1 rad/sec and also over a range of frequencies. The results are compared to those obtained from LTSpice.
The net list for Figure 7.1 was generated by LTSpice and show below:
R2 2 5 2
V1 1 0 0 AC 1
I1 4 0 9
V2 0 5 0
E1 3 0 1 4 2
F1 2 3 V2 2
R1 1 4 2
C1 1 2 1
L1 4 3 1
7.1 Load the net list
= '''
net_list R2 2 5 2
V1 1 0 1
I1 4 0 9
V2 0 5 0
E1 3 0 1 4 2
F1 2 3 V2 2
R1 1 4 2
C1 1 2 1
L1 4 3 1
'''
7.2 Call the symbolic modified nodal analysis function
= SymMNA.smna(net_list) report, network_df, i_unk_df, A, X, Z
Display the equations
# reform X and Z into Matrix type for printing
= Matrix(X)
Xp = Matrix(Z)
Zp = ''
temp for i in range(len(X)):
+= '${:s}$<br>'.format(latex(Eq((A*Xp)[i:i+1][0],Zp[i])))
temp
Markdown(temp)
\(- C_{1} s v_{2} + I_{V1} + v_{1} \left(C_{1} s + \frac{1}{R_{1}}\right) - \frac{v_{4}}{R_{1}} = 0\)
\(- C_{1} s v_{1} + I_{F1} + v_{2} \left(C_{1} s + \frac{1}{R_{2}}\right) - \frac{v_{5}}{R_{2}} = 0\)
\(I_{Ea1} - I_{F1} - I_{L1} = 0\)
\(I_{L1} - \frac{v_{1}}{R_{1}} + \frac{v_{4}}{R_{1}} = - I_{1}\)
\(- I_{V2} - \frac{v_{2}}{R_{2}} + \frac{v_{5}}{R_{2}} = 0\)
\(v_{1} = V_{1}\)
\(- v_{5} = V_{2}\)
\(- ea_{1} v_{1} + ea_{1} v_{4} + v_{3} = 0\)
\(I_{F1} - I_{V2} f_{1} = 0\)
\(- I_{L1} L_{1} s - v_{3} + v_{4} = 0\)
7.2.1 Netlist statistics
print(report)
Net list report
number of lines in netlist: 9
number of branches: 9
number of nodes: 5
number of unknown currents: 5
number of RLC (passive components): 4
number of inductors: 1
number of independent voltage sources: 2
number of independent current sources: 1
number of Op Amps: 0
number of E - VCVS: 1
number of G - VCCS: 0
number of F - CCCS: 1
number of H - CCVS: 0
number of K - Coupled inductors: 0
7.2.2 Connectivity Matrix
A
\(\displaystyle \left[\begin{matrix}C_{1} s + \frac{1}{R_{1}} & - C_{1} s & 0 & - \frac{1}{R_{1}} & 0 & 1 & 0 & 0 & 0 & 0\\- C_{1} s & C_{1} s + \frac{1}{R_{2}} & 0 & 0 & - \frac{1}{R_{2}} & 0 & 0 & 0 & 1 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & -1 & -1\\- \frac{1}{R_{1}} & 0 & 0 & \frac{1}{R_{1}} & 0 & 0 & 0 & 0 & 0 & 1\\0 & - \frac{1}{R_{2}} & 0 & 0 & \frac{1}{R_{2}} & 0 & -1 & 0 & 0 & 0\\1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & -1 & 0 & 0 & 0 & 0 & 0\\- ea_{1} & 0 & 1 & ea_{1} & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & - f_{1} & 0 & 1 & 0\\0 & 0 & -1 & 1 & 0 & 0 & 0 & 0 & 0 & - L_{1} s\end{matrix}\right]\)
7.2.3 Unknown voltages and currents
X
\(\displaystyle \left[ v_{1}, \ v_{2}, \ v_{3}, \ v_{4}, \ v_{5}, \ I_{V1}, \ I_{V2}, \ I_{Ea1}, \ I_{F1}, \ I_{L1}\right]\)
7.2.4 Known voltages and currents
Z
\(\displaystyle \left[ 0, \ 0, \ 0, \ - I_{1}, \ 0, \ V_{1}, \ V_{2}, \ 0, \ 0, \ 0\right]\)
7.2.5 Network dataframe
network_df
element | p node | n node | cp node | cn node | Vout | value | Vname | Lname1 | Lname2 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | V1 | 1 | 0 | NaN | NaN | NaN | 1.0 | NaN | NaN | NaN |
1 | V2 | 0 | 5 | NaN | NaN | NaN | 0.0 | NaN | NaN | NaN |
2 | R2 | 2 | 5 | NaN | NaN | NaN | 2.0 | NaN | NaN | NaN |
3 | I1 | 4 | 0 | NaN | NaN | NaN | 9.0 | NaN | NaN | NaN |
4 | Ea1 | 3 | 0 | 1 | 4 | NaN | 2.0 | NaN | NaN | NaN |
5 | F1 | 2 | 3 | NaN | NaN | NaN | 2.0 | V2 | NaN | NaN |
6 | R1 | 1 | 4 | NaN | NaN | NaN | 2.0 | NaN | NaN | NaN |
7 | C1 | 1 | 2 | NaN | NaN | NaN | 1.0 | NaN | NaN | NaN |
8 | L1 | 4 | 3 | NaN | NaN | NaN | 1.0 | NaN | NaN | NaN |
7.2.6 Unknown current dataframe
i_unk_df
element | p node | n node | |
---|---|---|---|
0 | V1 | 1 | 0 |
1 | V2 | 0 | 5 |
2 | Ea1 | 3 | 0 |
3 | F1 | 2 | 3 |
4 | L1 | 4 | 3 |
7.2.7 Build the network equations
# Put matrices into SymPy
= Matrix(X)
X = Matrix(Z)
Z
= Eq(A*X,Z) NE_sym
Turn the free symbols into SymPy variables.
str(NE_sym.free_symbols).replace('{','').replace('}','')) var(
\(\displaystyle \left( C_{1}, \ R_{1}, \ v_{4}, \ I_{V2}, \ V_{1}, \ I_{1}, \ L_{1}, \ I_{Ea1}, \ v_{1}, \ v_{3}, \ ea_{1}, \ s, \ f_{1}, \ V_{2}, \ v_{5}, \ I_{F1}, \ I_{V1}, \ v_{2}, \ R_{2}, \ I_{L1}\right)\)
7.3 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_{1} R_{2} V_{1} s + V_{2} f_{1} - V_{2}}{C_{1} R_{2} s - f_{1} + 1}\)
\(v_{3} = \frac{I_{1} L_{1} R_{1} ea_{1} s + R_{1} V_{1} ea_{1}}{L_{1} s + R_{1} ea_{1} + R_{1}}\)
\(v_{4} = \frac{- I_{1} L_{1} R_{1} s + L_{1} V_{1} s + R_{1} V_{1} ea_{1}}{L_{1} s + R_{1} ea_{1} + R_{1}}\)
\(v_{5} = - V_{2}\)
\(I_{V1} = \frac{- C_{1} I_{1} L_{1} R_{2} s^{2} + C_{1} L_{1} V_{1} f_{1} s^{2} - C_{1} L_{1} V_{1} s^{2} + C_{1} L_{1} V_{2} f_{1} s^{2} - C_{1} L_{1} V_{2} s^{2} + C_{1} R_{1} V_{1} ea_{1} f_{1} s - C_{1} R_{1} V_{1} ea_{1} s + C_{1} R_{1} V_{1} f_{1} s - C_{1} R_{1} V_{1} s + C_{1} R_{1} V_{2} ea_{1} f_{1} s - C_{1} R_{1} V_{2} ea_{1} s + C_{1} R_{1} V_{2} f_{1} s - C_{1} R_{1} V_{2} s - C_{1} R_{2} V_{1} s + I_{1} L_{1} f_{1} s - I_{1} L_{1} s + V_{1} f_{1} - V_{1}}{C_{1} L_{1} R_{2} s^{2} + C_{1} R_{1} R_{2} ea_{1} s + C_{1} R_{1} R_{2} s - L_{1} f_{1} s + L_{1} s - R_{1} ea_{1} f_{1} + R_{1} ea_{1} - R_{1} f_{1} + R_{1}}\)
\(I_{V2} = \frac{- C_{1} V_{1} s - C_{1} V_{2} s}{C_{1} R_{2} s - f_{1} + 1}\)
\(I_{Ea1} = \frac{- C_{1} I_{1} R_{1} R_{2} ea_{1} s - C_{1} I_{1} R_{1} R_{2} s - C_{1} L_{1} V_{1} f_{1} s^{2} - C_{1} L_{1} V_{2} f_{1} s^{2} - C_{1} R_{1} V_{1} ea_{1} f_{1} s - C_{1} R_{1} V_{1} f_{1} s - C_{1} R_{1} V_{2} ea_{1} f_{1} s - C_{1} R_{1} V_{2} f_{1} s + C_{1} R_{2} V_{1} s + I_{1} R_{1} ea_{1} f_{1} - I_{1} R_{1} ea_{1} + I_{1} R_{1} f_{1} - I_{1} R_{1} - V_{1} f_{1} + V_{1}}{C_{1} L_{1} R_{2} s^{2} + C_{1} R_{1} R_{2} ea_{1} s + C_{1} R_{1} R_{2} s - L_{1} f_{1} s + L_{1} s - R_{1} ea_{1} f_{1} + R_{1} ea_{1} - R_{1} f_{1} + R_{1}}\)
\(I_{F1} = \frac{- C_{1} V_{1} f_{1} s - C_{1} V_{2} f_{1} s}{C_{1} R_{2} s - f_{1} + 1}\)
\(I_{L1} = \frac{- I_{1} R_{1} ea_{1} - I_{1} R_{1} + V_{1}}{L_{1} s + R_{1} ea_{1} + R_{1}}\)
7.4 Construct a dictionary of element values
= SymMNA.get_part_values(network_df)
element_values
# display the component values
for k,v in element_values.items():
print('{:s} = {:s}'.format(str(k), str(v)))
V1 = 1.0
V2 = 0.0
R2 = 2.0
I1 = 9.0
ea1 = 2.0
f1 = 2.0
R1 = 2.0
C1 = 1.0
L1 = 1.0
7.5 DC operating point
Both V1 and I1 are active.
= NE_sym.subs(element_values)
NE = NE.subs({s:0}) NE_dc
Display the equations with numeric values.
= ''
temp for i in range(shape(NE_dc.lhs)[0]):
+= '${:s} = {:s}$<br>'.format(latex(NE_dc.rhs[i]),latex(NE_dc.lhs[i]))
temp
Markdown(temp)
\(0 = I_{V1} + 0.5 v_{1} - 0.5 v_{4}\)
\(0 = I_{F1} + 0.5 v_{2} - 0.5 v_{5}\)
\(0 = I_{Ea1} - I_{F1} - I_{L1}\)
\(-9.0 = I_{L1} - 0.5 v_{1} + 0.5 v_{4}\)
\(0 = - I_{V2} - 0.5 v_{2} + 0.5 v_{5}\)
\(1.0 = v_{1}\)
\(0 = - v_{5}\)
\(0 = - 2.0 v_{1} + v_{3} + 2.0 v_{4}\)
\(0 = I_{F1} - 2.0 I_{V2}\)
\(0 = - v_{3} + v_{4}\)
Solve for voltages and currents.
= solve(NE_dc,X) U_dc
Display the numerical solution
Six significant digits are displayed so that results can be compared to LTSpice.
= ['unknown', 'mag']
table_header = []
table_row
for name, value in U_dc.items():
str(name),float(value)])
table_row.append([
print(tabulate(table_row, headers=table_header,colalign = ('left','decimal'),tablefmt="simple",floatfmt=('5s','.6f')))
unknown mag
--------- ---------
v1 1.000000
v2 0.000000
v3 0.666667
v4 0.666667
v5 0.000000
I_V1 -0.166667
I_V2 0.000000
I_Ea1 -8.833333
I_F1 0.000000
I_L1 -8.833333
The node voltages and current through the sources are solved for. The Sympy generated solution matches the LTSpice results:
--- Operating Point ---
V(2): -2e-12 voltage
V(5): 0 voltage
V(1): 1 voltage
V(4): 0.666667 voltage
V(3): 0.666667 voltage
I(C1): 1e-12 device_current
I(F1): 2e-12 device_current
I(L1): -8.83333 device_current
I(I1): 9 device_current
I(R2): -1e-12 device_current
I(R1): 0.166667 device_current
I(E1): -8.83333 device_current
I(V1): -0.166667 device_current
I(V2): 1e-12 device_current
The results from LTSpice agree with the SymPy results.
7.5.1 AC analysis
Solve equations for \(\omega\) equal to 1 radian per second, s = 1j.
Need to set I1 = 0
= 0
element_values[I1] = NE_sym.subs(element_values)
NE = NE.subs({s:1j}) NE_w1
Display the equations with numeric values.
= ''
temp for i in range(shape(NE_w1.lhs)[0]):
+= '${:s} = {:s}$<br>'.format(latex(NE_w1.rhs[i]),latex(NE_w1.lhs[i]))
temp
Markdown(temp)
\(0 = I_{V1} + v_{1} \cdot \left(0.5 + 1.0 i\right) - 1.0 i v_{2} - 0.5 v_{4}\)
\(0 = I_{F1} - 1.0 i v_{1} + v_{2} \cdot \left(0.5 + 1.0 i\right) - 0.5 v_{5}\)
\(0 = I_{Ea1} - I_{F1} - I_{L1}\)
\(0 = I_{L1} - 0.5 v_{1} + 0.5 v_{4}\)
\(0 = - I_{V2} - 0.5 v_{2} + 0.5 v_{5}\)
\(1.0 = v_{1}\)
\(0 = - v_{5}\)
\(0 = - 2.0 v_{1} + v_{3} + 2.0 v_{4}\)
\(0 = I_{F1} - 2.0 I_{V2}\)
\(0 = - 1.0 i I_{L1} - v_{3} + v_{4}\)
Solve for voltages and currents.
= solve(NE_w1,X) U_w1
Display the numerical solution
Six significant digits are displayed so that results can be compared to LTSpice.
= ['unknown', 'mag','phase, deg']
table_header = []
table_row
for name, value in U_w1.items():
str(name),float(abs(value)),float(arg(value)*180/np.pi)])
table_row.append([
print(tabulate(table_row, headers=table_header,colalign = ('left','decimal','decimal'),tablefmt="simple",floatfmt=('5s','.6f','.6f')))
unknown mag phase, deg
--------- -------- ------------
v1 1.000000 0.000000
v2 0.894427 -26.565051
v3 0.657596 -9.462322
v4 0.677834 4.573921
v5 0.000000 nan
I_V1 0.294086 -36.027373
I_V2 0.447214 153.434949
I_Ea1 0.738882 149.683220
I_F1 0.894427 153.434949
I_L1 0.164399 -9.462322
The LTSpice solution is shown below:
--- AC Analysis ---
frequency: 0.159155 Hz
V(2): mag: 0.894427 phase: -26.5651° voltage
V(5): mag: 0 phase: 0° voltage
V(1): mag: 1 phase: 0° voltage
V(4): mag: 0.677834 phase: 4.57392° voltage
V(3): mag: 0.657596 phase: -9.46232° voltage
I(C1): mag: 0.447214 phase: 153.435° device_current
I(F1): mag: 0.894427 phase: 153.435° device_current
I(L1): mag: 0.164399 phase: -9.46232° device_current
I(I1): mag: 0 phase: 0° device_current
I(R2): mag: 0.447214 phase: -26.5651° device_current
I(R1): mag: 0.164399 phase: -9.46232° device_current
I(E1): mag: 0.738882 phase: 149.683° device_current
I(V1): mag: 0.294086 phase: -36.0274° device_current
I(V2): mag: 0.447214 phase: 153.435° device_current
7.5.2 AC Sweep
Looking at node 2 voltage and comparing the results with those obtained from LTSpice. Thr frequenct weep is from 0.01 Hz to 1 Hz.
= NE_sym.subs(element_values) NE
Display the equations with numeric values.
= ''
temp for i in range(shape(NE.lhs)[0]):
+= '${:s} = {:s}$<br>'.format(latex(NE.rhs[i]),latex(NE.lhs[i]))
temp
Markdown(temp)
\(0 = I_{V1} - 1.0 s v_{2} + v_{1} \cdot \left(1.0 s + 0.5\right) - 0.5 v_{4}\)
\(0 = I_{F1} - 1.0 s v_{1} + v_{2} \cdot \left(1.0 s + 0.5\right) - 0.5 v_{5}\)
\(0 = I_{Ea1} - I_{F1} - I_{L1}\)
\(0 = I_{L1} - 0.5 v_{1} + 0.5 v_{4}\)
\(0 = - I_{V2} - 0.5 v_{2} + 0.5 v_{5}\)
\(1.0 = v_{1}\)
\(0 = - v_{5}\)
\(0 = - 2.0 v_{1} + v_{3} + 2.0 v_{4}\)
\(0 = I_{F1} - 2.0 I_{V2}\)
\(0 = - 1.0 I_{L1} s - v_{3} + v_{4}\)
Solve for voltages and currents.
= solve(NE,X) U_ac
7.5.3 Plot the voltage at node 2
= U_ac[v2] H
= fraction(H) #returns numerator and denominator
num, denom
# convert symbolic to numpy polynomial
= np.array(Poly(num, s).all_coeffs(), dtype=float)
a = np.array(Poly(denom, s).all_coeffs(), dtype=float)
b = (a, b) system
#x = np.linspace(0.01*2*np.pi, 1*2*np.pi, 200, endpoint=True)
= np.logspace(-2, 0, 200, endpoint=False)*2*np.pi
x = signal.bode(system, w=x) # returns: rad/s, mag in dB, phase in deg w, mag, phase
Load the csv file of node 2 voltage over the sweep range and plot along with the results obtained from SymPy.
= 'test_3.csv' # data from LTSpice
fn = np.genfromtxt(fn, delimiter=',') LTSpice_data
# initaliaze some empty arrays
= np.zeros(len(LTSpice_data))
frequency = np.zeros(len(LTSpice_data)).astype(complex)
voltage
# convert the csv data to complex numbers and store in the array
for i in range(len(LTSpice_data)):
= LTSpice_data[i][0]
frequency[i] = LTSpice_data[i][1] + LTSpice_data[i][2]*1j voltage[i]
= plt.subplots()
fig, ax1 'magnitude, dB')
ax1.set_ylabel('frequency, Hz')
ax1.set_xlabel(
20*np.log10(np.abs(voltage)),'-r') # Bode magnitude plot
plt.semilogx(frequency, /(2*np.pi), mag,'-b') # Bode magnitude plot
plt.semilogx(w
='y')
ax1.tick_params(axis-30,20))
ax1.set_ylim((
plt.grid()
# instantiate a second y-axes that shares the same x-axis
= ax1.twinx()
ax2 = 'tab:blue'
color
*180/np.pi,':',color=color) # Bode phase plot
plt.semilogx(frequency, np.angle(voltage)/(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()
= plt.subplots()
fig, ax1 'magnitude difference')
ax1.set_ylabel('frequency, Hz')
ax1.set_xlabel(
0:-1], np.abs(voltage[0:-1])-10**(mag/20),'-r') # Bode magnitude plot
plt.semilogx(frequency[#plt.semilogx(w/(2*np.pi), mag,'-b') # Bode magnitude plot
='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
0:-1], np.unwrap(2*np.angle(voltage[0:-1])/2) *180/np.pi - phase,':',color=color) # Bode phase plot
plt.semilogx(frequency[#plt.semilogx(w/(2*np.pi), phase,':',color='tab:red') # Bode phase plot
'phase difference, deg',color=color)
ax2.set_ylabel(='y', labelcolor=color)
ax2.tick_params(axis#ax2.set_ylim((-5,25))
'Difference between LTSpice and Python results')
plt.title( plt.show()
The SymPy and LTSpice results overlay each other. The scale for the magnitude is \(10^{-15}\) and \(10^{-13}\) for the phase indicating the numerical difference is very small.