SymPy allows quick and (relatively ðŸ˜œ) easy manipulation of algebraic expressions. But it can do so much more! Using the `sympy.physics.control`

toolbox, you can very inspect linear time-invariant systems in a very comfortable way.

I was looking at a paper (Janiszowski, 1993) which features three transfer functions of different properties and wanted to look at their pole-zero diagrams as well as their Bode plots in real frequency. The transfer functions where the following:

(1)

Drawing the pole-zero diagrams from this representation is easy:

- Zeros: Set numerator to zero
- Poles: Set denominator (or its individual terms) to zero

Drawing the Bode diagram however would’ve involved some basic programming. But neither is necessary.

```
import sympy as sy
from sympy.physics.control.lti import TransferFunction as TF
from sympy.physics.control import bode_plot, pole_zero_plot
s = sy.symbols("s", complex=True)
G1 = (2 + 42*s)/((1 + 2*s)*(1 + 40*s))
tf = TF(*G.as_numer_denom(), s)
bode_plot(tf)
pole_zero_plot(tf)
```

Well, that was easy. `TransferFunction`

(abbreviated with `TF`

in my examples) requires numerator and denominator to be passed as separate arguments. `sympy_expr.as_numer_denom()`

is convenient, as it returns a `(numerator, denominator)`

tuple. Using the asterisk expands this.

Now, what else can we do with this? Look at RLC resonators, for example:

The reactance of the inductor and capacitor are given by

(2)

where *s* is basically the complex frequency (we’re looking at this with Laplace eyes). Now, off to SymPy we go:

```
R, C, L = sy.symbols("R, C, L", real=True, positive=True)
Xc, Xl = 1/(s*C), s*L
def par(a, b):
# Shorthand for a parallel circuit
return a*b/(a+b)
G = par(Xc, par(R, Xl)).ratsimp()
G = G.subs({R: 1e3, L: 1e-6, C: 1e-6})
tf = TF(*G.as_numer_denom(), s)
bode_plot(tf, 5, 7)
pole_zero_plot(tf)
```

For visualisation we choose R = 1 kÎ©, L = 1 ÂµH and C = 1 ÂµF.

Why is this useful? Well, because it is quick and robust and once you have your transfer function typed out, you get the impulse and step responses for free:

```
import sympy as sy
from sympy.physics.control import impulse_response_plot
from sympy.physics.control import step_response_plot
s = sy.symbols("s", complex=True)
G1 = (2+42*s)/((1+2*s)*(1+40*s))
G2 = (5-60*s)/((1+4*s)*(1+40*s))
G3 = 4*(1+s)/(1+4*s+8*s**2)
for G in [G1, G2, G3]:
tf = TF(*G.as_numer_denom(), s)
bode_plot(tf)
pole_zero_plot(tf)
step_response_plot(tf, upper_limit=60)
impulse_response_plot(tf, upper_limit=60)
```

Note: SymPy’s adaptive plotting is not particularly good at plotting oscillations, so the impulse response of the RLC circuit above will look ugly.