I am an analog circuit designer. Recently, I started studying Python as a hobby.
Numerical analysis circuit simulators using SPICE are often used in analog circuit design work. However, the numerical analysis simulator alone does not deepen the understanding of the circuit being designed. Sometimes it is necessary to simplify the circuit you are designing, formulate a circuit equation, and solve it. Solving circuit equations is a laborious task. It's okay to solve it for the time being, but I got results for the time, such as there was a calculation error and I could not get a useful result, the formula I got was complicated and I could not get insight and I did not have to solve it. It can be said that it is difficult to get rid of. So, this time, I made it possible to solve the circuit equation using Sympy. When creating the program, I was conscious of reducing the number of human input parts as much as possible.
・ Circuit designers tired of solving circuit design ・ I have played with python and Jupyter-notebook ・ I have touched sympy a little
The assumed situation is as follows "I've done it until I draw a circuit diagram in my notebook and formulate the equations. There are so many equations that it will take time to solve them. I don't know if I can get meaningful results even if I solve them ... Alright, let sympy do it! "
Python: 3.7.4、SymPy: 1.6.2
As a simple example, let's calculate the transfer function of the circuit shown below. Circuit component constants are symbols rather than numbers.
First, let's formulate the circuit equation by ourselves as shown in the figure below. When it comes to letting sympy solve circuit equations, you don't have to devise ways to reduce the number of equations, which saves your head energy.
After confirming that the unknowns (here ZP, ZM, IT, VOUT) and the number of equations match, the circuit equation creation is complete.
From here on, let's run it on Jupyter-notebook.
Suddenly, the whole code is below.
All chords
#Enter the circuit equation
ce = """
ZP = RP / (1 + s*CP*RP)
ZM = RM / (1 + s*CM*RM)
VIN = (s*LP + ZP + ZM)*IT
VOUT = ZM * IT
"""
#Enter an unknown
uk = "ZP, ZM, IT, VOUT"
#Enter the variable you want to find
wn = "VOUT"
#----------------------------------------------------
#Below, there is no place for the user to enter
#Initial setting
from sympy import *
from IPython.display import Math
from collections import Counter
#Extract the symbol from the circuit equation and declare it as Symbol
rep_char = ["+", "-", "*", "/", "s", "=", "\n", "(", ")"]
ce_rep = ce
for i in range(len(rep_char)):
ce_rep = ce_rep.replace(rep_char[i], " ")
ce_sym = ce_rep.split()
ce_sym = list(Counter(ce_sym).keys())
for i in reversed(range(len(ce_sym))): #Clear the numbers in the list
if ce_sym[i].isdecimal():
del ce_sym[i]
s = Symbol("s", real=True)
for i in range(len(ce_sym)):
exec(ce_sym[i] + " = Symbol(\"" + ce_sym[i] + "\", real=True)")
#Generate an array for TeX display
ce_tex = []
for i in range(len(ce_sym)):
if len(ce_sym[i]) == 1:
ce_tex.append(ce_sym[i][0])
else:
ce_tex.append(ce_sym[i][0] + "_{" + ce_sym[i][1:] + "}")
#Generate circuit equations and unknown symbol lists
start = 3
ind_eq = -1
ind_rt = 2
ce_sol = []
while True:
ind_eq = ce.find("=", ind_eq+1)
ind_rt = ce.find("\n", ind_rt+1)
if ind_rt == -1:
break
exec("ce_sol.append(" + ce[start:ind_eq] + "-(" + ce[ind_eq+1: ind_rt] + "))")
start=ind_rt + 1
exec("uk_sol = " + uk)
exec("wn_sol = " + wn)
#Solve and organize equations
if len(uk_sol) != len(ce_sol):
print("Align the number of unknowns with the number of equations.")
else:
sol = solve(ce_sol, uk_sol, dict=True)[0][wn_sol]
#Organize the denominator numerator
nu = collect(expand(numer(sol)), s) #Numerator, molecule
de = collect(expand(denom(sol)), s) #denominator, denominator
sol = nu / de
#Displaying expressions
sol_tex = latex(sol)
for i in range(len(ce_sym)):
sol_tex = sol_tex.replace(latex(ce_sym[i]), ce_tex[i])
display(Math(sol_tex))
If you do this, you will get the following results:
\displaystyle \frac{C_{P} R_{M} R_{P} V_{IN} s + R_{M} V_{IN}}{C_{M} C_{P} L_{P} R_{M} R_{P} s^{3} + R_{M} + R_{P} + s^{2} \left(C_{M} L_{P} R_{M} + C_{P} L_{P} R_{P}\right) + s \left(C_{M} R_{M} R_{P} + C_{P} R_{M} R_{P} + L_{P}\right)}
Let's explain the flow of the program and its details.
#Enter the circuit equation
ce = """
ZP = RP / (1 + s*CP*RP)
ZM = RM / (1 + s*CM*RM)
VIN = (s*LP + ZP + ZM)*IT
VOUT = ZM * IT
"""
#Enter an unknown
uk = "ZP, ZM, IT, VOUT"
#Enter the variable you want to find
wn = "VOUT"
Enter the circuit equation, the unknown, and the variable you want to find. When entering circuit equations, start with a capital letter for parameters other than s. This is because it is inconvenient to use lowercase letters in the processing when displaying the calculation result in TeX.
python
from sympy import *
from IPython.display import Math
from collections import Counter
Import the module. No special commentary is needed.
python
rep_char = ["+", "-", "*", "/", "s", "=", "\n", "(", ")"]
ce_rep = ce
for i in range(len(rep_char)):
ce_rep = ce_rep.replace(rep_char[i], " ")
ce_sym = ce_rep.split()
ce_sym = list(Counter(ce_sym).keys())
for i in reversed(range(len(ce_sym))): #Clear the numbers in the list
if ce_sym[i].isdecimal():
del ce_sym[i]
Here, the following processing is performed __ (i) The circuit equation ce is input in STEP1 __ ZP = RP / (1 + sCPRP) ZM = RM / (1 + sCM+RM) VIN = (sLP + ZP + ZM)*IT VOUT = ZM * IT
__ (ii) Replace operators (+-* /), s, line feeds (\ n), etc. with spaces from the input circuit equation (ce in STEP1) __ ce_rep: ZP RP 1 CP RP ZM RM 1 CM RM VIN LP ZP ZM IT VOUT ZM IT
__ (iii) Extract the symbols that appear in the equation __ ce_sym: ['ZP', 'RP', '1', 'CP', 'ZM', 'RM', 'CM', 'VIN', 'LP', 'IT', 'VOUT']
__ (iii) Find the number from the list obtained above and delete it (in this case 1 is deleted) __ ce_sym: ['ZP', 'RP', 'CP', 'ZM', 'RM', 'CM', 'VIN', 'LP', 'IT', 'VOUT']
Now that we have a list of the symbols used in the circuit equations in the above process Declare it as a Sympy Symbol.
python
s = Symbol("s", real=True)
for i in range(len(ce_sym)):
exec(ce_sym[i] + " = Symbol(\"" + ce_sym[i] + "\", real=True)")
Code running in __exec: __ ZP = Symbol("ZP", real=True) RP = Symbol("RP", real=True) CP = Symbol("CP", real=True) ZM = Symbol("ZM", real=True) RM = Symbol("RM", real=True) CM = Symbol("CM", real=True) VIN = Symbol("VIN", real=True) LP = Symbol("LP", real=True) IT = Symbol("IT", real=True) VOUT = Symbol("VOUT", real=True)
python
ce_tex = []
for i in range(len(ce_sym)):
if len(ce_sym[i]) == 1:
ce_tex.append(ce_sym[i][0])
else:
ce_tex.append(ce_sym[i][0] + "_{" + ce_sym[i][1:] + "}")
I want to display the output result in TeX, so I will create a list for this. Based on the list of ce_sym, the list ce_tex to be subscripted after the second character is generated [^ 1]. Example: VOUT → V_ {OUT}, $ V_ {OUT} $
ce_tex: ['Z_{P}', 'R_{P}', 'C_{P}', 'Z_{M}', 'R_{M}', 'C_{M}', 'V_{IN}', 'L_{P}', 'I_{T}', 'V_{OUT}']
__ (Reference) ce_sym: __ obtained in STEP3 ['ZP', 'RP', 'CP', 'ZM', 'RM', 'CM', 'VIN', 'LP', 'IT', 'VOUT']
[^ 1]: Note that if you use the symbol BW1, the Tex display will not be $ BW_1 $, but $ B_ {W1} $.
python
start = 3
ind_eq = -1
ind_rt = 2
ce_sol = []
while True:
ind_eq = ce.find("=", ind_eq+1)
ind_rt = ce.find("\n", ind_rt+1)
if ind_rt == -1:
break
exec("ce_sol.append(" + ce[start:ind_eq] + "-(" + ce[ind_eq+1: ind_rt] + "))")
start=ind_rt + 1
exec("uk_sol = " + uk)
exec("wn_sol = " + wn)
The equation and the unknown symbol list are generated by the following processing.
__ (i) The circuit equation ce is input in STEP1 __ ce: ZP = RP / (1 + sCPRP) ZM = RM / (1 + sCMRM) VIN = (s*LP + ZP + ZM)*IT VOUT = ZM * IT
__ (ii) Find the equal'=' and the line feed'\ n'from ce, and create a list with the right side moved to the left side __ ce_sol: -RP/(CPRPs + 1) + ZP, -RM/(CMRMs + 1) + ZM, -IT*(LPs + ZM + ZP) + VIN, -ITZM + VOUT
__ (iii) Execute the following code for the unknown (uk) and the variable (wn) you want to find __ uk_sol = ZP, ZM, IT, VOUT wn_sol = VOUT
This is the part that finally solves the equation.
python
if len(uk_sol) != len(ce_sol):
print("Align the number of unknowns with the number of equations.")
else:
sol = solve(ce_sol, uk_sol, dict=True)[0][wn_sol]
#Organize the denominator numerator
nu = collect(expand(numer(sol)), s) #Numerator, molecule
de = collect(expand(denom(sol)), s) #denominator, denominator
sol = nu / de
If the number of unknowns and the number of equations are not the same, a solution cannot be obtained, so we check this first.
The equation is solved by the following code.
sol = solve(ce_sol, uk_sol, dict=True)[0][wn_sol]
If you execute solve (ce_sol, uk_sol, dict = True), you will get all the unknown solutions as below. By adding [0] [wn_sol], only the solution (VOUT this time) that you want to find is extracted.
Result of executing ___sol = solve (ce_sol, uk_sol, dict = True): __ ZP: RP/(CPRPs + 1) ZM: RM/(CMRMs + 1) IT: VIN * (CM * RM * s + 1) * (CP ~ omitted ~ VOUT: RM * VIN * (CP * RP * s + 1 ~ omitted ~
After finding the solution, the denominator and numerator are organized for s.
The obtained solution is difficult to read as shown below.
sol:
#Displaying expressions
sol_tex = latex(sol)
for i in range(len(ce_sym)):
sol_tex = sol_tex.replace(latex(ce_sym[i]), ce_tex[i])
display(Math(sol_tex))
Therefore, in order to make the display of the equation easier to see, it is converted to the TeX display. For example, if it is VOUT, replace it with V_ {OUT}.
The final result is as follows, and the readability has been improved by using TeX display.
The formula is replaced to display TeX, but it may behave unexpectedly depending on the symbol name. For example, using the symbol ac replaces \ fr ac in TeX code. For this reason, symbols should basically start with a capital letter.
That is all for the explanation. Since the user inputs only the circuit equation, the unknown, and the variable to be obtained, the circuit equation can be solved with the minimum effort required. The effort saved can be used to interpret the results obtained. For example, you could simplify the formula from the magnitude relation of the terms.
Humans set up the circuit equation this time, but I think that even setting up the circuit equation can be troublesome (no time). In this case, you can also automate the formulation of circuit equations by using a python package called Lcapy. All the user has to do is draw a schematic. I wrote a commentary article about this, so if you are interested, please have a look there as well.
[Circuit x Python] How to enable the linear circuit analysis package Lcapy [Circuit x Python] How to find the transfer function of a circuit using Lcapy [Circuit x Python] How to expand and calculate transfer function using Lcapy
Recommended Posts