# SLiCAP pole-zero analysis

Accurate pole-zero analysis is indispensible for investigating the stability of dynamic systems. SLiCAP supports both numeric and symbolic pole-zero analysis. Symbolic pole-zero analysis is supported for lower-order rational functions of the Laplace variable and for functions implemented in sympy. Numeric pole-zero analysis is implemented for Laplace rational functions of any order. SLiCAP uses numeric analysis if no other symbolic variables than the Laplace variable `ini.laplace=s` are found in the expression.

SLiCAP has three instructions related to pole-zero analysis:

1. doPoles() returns the solutions of the Laplace variable of the denominator of a transfer function.

2. doZeros() returns the solutions of the Laplace variable of the numerator of a transfer function.

3. doPZ() returns the zero frequecy gain, the poles, and the zeros of a transfer function. Poles and zeros that coincide withing the display accurary `ini.disp` are canceled out.

   Important

   `doPZ()` does not return non-observable and non-controllable poles. Use `doPoles()` and `doZeros()` to obtain information about non-observable and non-controllable states.

   - Non-controllable state: the input signal(s) cannot change the state.

   - Non-observable state: the state cannot be observed at the output(s)

SLiCAP output displayed on this manual page, is generated with the script: `laplace.py`, imported by `Manual.py`.

    1#!/usr/bin/env python3
    2# -*- coding: utf-8 -*-
    3
    4"""
    5pz.py: SLiCAP scripts for the HTML help file
    6"""
    7import SLiCAP as sl

The following circuit is used for this manual page:

    9pz_network = sl.makeCircuit("kicad/pzNetwork/pzNetwork.kicad_sch")

With a differential voltage detector between nodes (1) and (2), and the signal source set to `V1`, the transfer has four poles and three zeros. Two zeros coincide with two poles:

- The pole introduced by C2 is not controllable and thus canceled by a zero.

- The pole introduced by \(\tau\) is not observable and thus canceled by a zero.

## Obtain all the poles of a transfer

### Symbolic poles analysis

    11pResult    = sl.doPoles(pz_network)
    12print(pResult.poles)

This yields:

    [-1/(C_b*R_b), -1/tau, (-C_a*R_a - C_a*R_d - sqrt(C_a**2*R_a**2 + 2*C_a**2*R_a*R_d + C_a**2*R_d**2 - 4*C_a*L))/(2*C_a*L), (-C_a*R_a - C_a*R_d + sqrt(C_a**2*R_a**2 + 2*C_a**2*R_a*R_d + C_a**2*R_d**2 - 4*C_a*L))/(2*C_a*L)]

Rendered with the formatter:

Table 14 Poles
# | \(f\) [Hz]
--- | ---
\(p_{1}\) | \(- \frac{0.5}{\pi C_{b} R_{b}}\)
\(p_{2}\) | \(- \frac{0.5}{\pi \tau}\)
\(p_{3}\) | \(\frac{0.25 \left(- C_{a} R_{a} - C_{a} R_{d} - \left(C_{a} \left(C_{a} R_{a}^{2} + 2 C_{a} R_{a} R_{d} + C_{a} R_{d}^{2} - 4 L\right)\right)^{0.5}\right)}{\pi C_{a} L}\)
\(p_{4}\) | \(\frac{0.25 \left(- C_{a} R_{a} - C_{a} R_{d} + \left(C_{a} \left(C_{a} R_{a}^{2} + 2 C_{a} R_{a} R_{d} + C_{a} R_{d}^{2} - 4 L\right)\right)^{0.5}\right)}{\pi C_{a} L}\)

Important

Poles and zeros returned by the instruction are always in [rad/s]. The units of poles and zeros displayed in tables HTML are governed by `ini.hz=True` (default).

### Numeric poles analysis

    14pResult_num = sl.doPoles(pz_network, pardefs="circuit")
    15print(pResult_num.poles)

This yields:

    [-30000.+10000.j, -30000.-10000.j, -100000.+0.j, -10000000.+0.j]

Rendered with the formatter:

Table 15 Poles
# | Re [Hz] | Im [Hz] | \(f\) [Hz] | Q
--- | --- | --- | --- | ---
\(p_{0}\) | \(-4775.0\) | \(1592.0\) | \(5033.0\) | \(0.5271\)
\(p_{0}\) | \(-4775.0\) | \(-1592.0\) | \(5033.0\) | \(0.5271\)
\(p_{0}\) | \(-1.592 \cdot 10^{4}\) | \(0\) | \(1.592 \cdot 10^{4}\) |
\(p_{0}\) | \(-1.592 \cdot 10^{6}\) | \(0\) | \(1.592 \cdot 10^{6}\) |

## Obtain all the zeros of a transfer

### Symbolic zero analysis

    17zResult     = sl.doZeros(pz_network)
    18print(zResult.zeros)

This yields:

    [-1/(C_b*R_b), -1/tau, -1/(C_a*R_d)]

Rendered with the formatter:

Table 16 Zeros
# | \(f\) [Hz]
--- | ---
\(z_{1}\) | \(- \frac{0.5}{\pi C_{b} R_{b}}\)
\(z_{2}\) | \(- \frac{0.5}{\pi \tau}\)
\(z_{3}\) | \(- \frac{0.5}{\pi C_{a} R_{d}}\)

### Numeric zero analysis

    20zResult_num = sl.doZeros(pz_network, pardefs="circuit")
    21print(zResult_num.zeros)

This yields:

    [-2.0000e+04, -1.0000e+05, -1.0000e+07]

Rendered with the formatter:

Table 17 Zeros
# | Re [Hz] | Im [Hz] | \(f\) [Hz] | Q
--- | --- | --- | --- | ---
\(z_{0}\) | \(-3183.0\) | \(0\) | \(3183.0\) |
\(z_{0}\) | \(-1.592 \cdot 10^{4}\) | \(0\) | \(1.592 \cdot 10^{4}\) |
\(z_{0}\) | \(-1.592 \cdot 10^{6}\) | \(0\) | \(1.592 \cdot 10^{6}\) |

## Obtain the observable and controllable poles and zeros of a transfer

### Symbolic pole-zero analysis

    23pzResult   = sl.doPZ(pz_network)
    24print(pzResult.DCvalue)
    25print(pzResult.poles)
    26print(pzResult.zeros)

This yields:

    1
    [(-C_a*R_a - C_a*R_d - sqrt(C_a**2*R_a**2 + 2*C_a**2*R_a*R_d + C_a**2*R_d**2 - 4*C_a*L))/(2*C_a*L), (-C_a*R_a - C_a*R_d + sqrt(C_a**2*R_a**2 + 2*C_a**2*R_a*R_d + C_a**2*R_d**2 - 4*C_a*L))/(2*C_a*L)]
    [-1/(C_a*R_d)]

Rendered with the formatter:

DC value of gain: \(1\)

Table 18 Observable poles and zeros
# | \(f\) [Hz]
--- | ---
\(p_{1}\) | \(\frac{0.25 \left(- C_{a} R_{a} - C_{a} R_{d} - \left(C_{a} \left(C_{a} R_{a}^{2} + 2 C_{a} R_{a} R_{d} + C_{a} R_{d}^{2} - 4 L\right)\right)^{0.5}\right)}{\pi C_{a} L}\)
\(p_{2}\) | \(\frac{0.25 \left(- C_{a} R_{a} - C_{a} R_{d} + \left(C_{a} \left(C_{a} R_{a}^{2} + 2 C_{a} R_{a} R_{d} + C_{a} R_{d}^{2} - 4 L\right)\right)^{0.5}\right)}{\pi C_{a} L}\)
\(z_{1}\) | \(- \frac{0.5}{\pi C_{a} R_{d}}\)

### Numeric pole-zero analysis

    28pzResult_num   = sl.doPZ(pz_network, pardefs="circuit")
    29sl.listPZ(pzResult_num)

The `listPZ()` function displays a table in the console output:

    DC value of gain: 1.00e+00

    Poles of gain:

     n  Real part [Hz]  Imag part [Hz]  Frequency [Hz]     Q [-]
    --  --------------  --------------  --------------  --------
     0       -4.77e+03        1.59e+03        5.03e+03   5.27e-1
     1       -4.77e+03       -1.59e+03        5.03e+03   5.27e-1

    Zeros of gain:

     n  Real part [Hz]  Imag part [Hz]  Frequency [Hz]     Q [-]
    --  --------------  --------------  --------------  --------
     0       -5.07e+02        0.00e+00        5.07e+02

Rendered with the formatter:

DC value of gain: \(1\)

Table 19 Observable poles and zeros
# | Re [Hz] | Im [Hz] | \(f\) [Hz] | Q
--- | --- | --- | --- | ---
\(p_{0}\) | \(-4775.0\) | \(1592.0\) | \(5033.0\) | \(0.5271\)
\(p_{0}\) | \(-4775.0\) | \(-1592.0\) | \(5033.0\) | \(0.5271\)
\(z_{0}\) | \(-3183.0\) | \(0\) | \(3183.0\) |

## Return attributes

Important

Calculation of the poles requires calculation of the denominator of a transfer. Hence, SLiCAP also executes the instruction `doDenom()` and returns the Laplace poly of the denominator (= determinant of the MNA matrix = ‘characteristic equation’) in the `.denom` attribute of the result. It also places the MNA matrix in the `.M` attribute of the returned instruction object.

Calculation of the zeros requires calculation of the numerator of a transfer. Hence, SLiCAP also executes the instruction `doNumer()` and returns the Laplace poly of the numerator in the `.numer` attribute of the result. It also places the MNA matrix in the `.M` in the returned instruction object.

Calculation of the poles and the zeros requires all of the above, and the following attributes are available in the returned object:

    31M        = pzResult.M
    32Iv       = pzResult.Iv
    33Dv       = pzResult.Dv
    34denom    = pzResult.denom
    35numer    = pzResult.numer
    36transfer = pzResult.laplace

## Display equations on HTML pages and in LaTeX documents

The report module Create reports, discusses how HTML snippets and LaTeX snippets can be created for variables, expressions, equations and tables.

As a matter of fact, all equations, tables and figures on this page are created with this module:

    38# Generate RST snippets for the Help file
    39rst = sl.RSTformatter()
    40
    41rst.pz(pResult     , caption="Poles").save("table-P")
    42rst.pz(pResult_num , caption="Poles").save("table-Pnum")
    43rst.pz(zResult     , caption="Zeros").save("table-Z")
    44rst.pz(zResult_num , caption="Zeros").save("table-Znum")
    45rst.pz(pzResult    , caption="Observable poles and  zeros").save("table-PZ")
    46rst.pz(pzResult_num, caption="Observable poles and  zeros").save("table-PZnum")

## Plot pole-zero patterns and root-locus

SLiCAP plot functions are discussed in Create plots