transfer functions

consider the linear, time-invariant system:

with:

  • input $U(s)=\mathcal{L}[u(t)]$
  • output $Y(s)=\mathcal{L}[y(t)]$

the output for an input is characterized by a transfer function $g(s)=Y(s)/U(s)$.

here, $\mathcal{L}[\cdot]$ is the Laplace transform that maps a function in the time domain $t\in\mathbb{R}$ into the frequency domain $s\in\mathbb{C}$.

a data structure, TransferFunction, represents a transfer function.

constructing a transfer function

for example, consider the transfer function $g(s)=\dfrac{5s+1}{s^2 + 4s+5}$.

constructor 1. we can construct $g(s)$ in an intuitive way that resembles the algebraic expression:

g = (5 * s + 1) / (s ^ 2 + 4 * s + 5) # construction method 1
# output
     5.0*s + 1.0
---------------------
1.0*s^2 + 4.0*s + 5.0

constructor 2. alternatively, we can construct a TransferFunction using the coefficients associated with the powers of $s$ in the polynomials composing the numerator and denominator, respectively, of the rational function $g(s)$. coefficients of the highest powers of $s$ are listed first.

g = TransferFunction([5, 1], [1, 4, 5]) # construction method 2
Note

under the hood, s == TransferFunction([1, 0], [1]).

accessing attributes of a transfer function

as rational functions associated with a time delay, each TransferFunction data structure has a numerator, denominator, and time_delay attribute. access as follows:

g.numerator
# output
Polynomial(1.0 + 5.0*s)
g.denominator
# output
Polynomial(5.0 + 4.0*s + 1.0*s^2)
g.time_delay
# output
0.0

g.numerator and g.denominator are Polynomials from Polynomials.jl.

time delays

to construct a transfer function with a time delay, such as $g(s)=\dfrac{3}{2s+1}e^{-2s}$...

θ = 2.0                              # time delay
g = 3 / (2 * s + 1) * exp(-θ * s)    # construction method 1
g = TransferFunction([3], [2, 1], θ) # construction method 2
# output
    3.0
----------- e^(-2.0*s)
2.0*s + 1.0

zeros, poles, k-factor representation

we can write any transfer function $g(s)$ in terms of its poles ($p_j$), zeros ($z_j$), k-factor ($k$), and time delay ($\theta$):

\[g(s)=k\dfrac{\Pi_j (s-z_j)}{\Pi_j(s-p_j)}e^{-\theta s}\]

the scalar factor $k$ allows us to uniquely specify a transfer function in terms of its poles, zeros, and time delay. note that the $k$-factor is not equal to the zero-frequency gain.

for example, consider:

\[g(s)=\dfrac{5s+1}{s^2 + 4s+5}=5\dfrac{(s+1/5)}{(s+2+i)(s+2-i)}\]

constructing a transfer function from its zeros, poles and k-factor

g = zeros_poles_k([-1/5], [-2 + im, -2 - im], 5.0, time_delay=0.0)  # construction method 3
# output
     5.0*s + 1.0
---------------------
1.0*s^2 + 4.0*s + 5.0

im is the imaginary number $i$. see the Julia docs on complex numbers.

computing the poles, zeros, and k-factor of a transfer function

g = (5 * s + 1) / (s ^ 2 + 4 * s + 5)
z, p, k = zeros_poles_k(g)
# output
([-0.2], ComplexF64[-2.0 - 1.0im, -2.0 + 1.0im], 5.0)

transfer function algebra

add +, subject -, multiply *, and divide / transfer functions.

g₁ = 3 / (s + 2)
g₂ = 1 / (s + 4)
g_product = g₁ * g₂
# output
         3.0
---------------------
1.0*s^2 + 6.0*s + 8.0
g_sum = g₁ + g₂
# output
    4.0*s + 14.0
---------------------
1.0*s^2 + 6.0*s + 8.0

evaluate a transfer function at a complex number

for example, to evaluate $g(s)=\dfrac{4}{s+2}$ at $s=-2+i$:

g = 4 / (s + 2)
evaluate(g, - 2 + im)
# output
0.0 - 4.0im

zero-frequency gain of a transfer function

compute the zero-frequency gain of a transfer function $g(s)$, which is $g(s)$ evaluated at $s=0$, as follows:

g = (5 * s + 1) / (s ^ 2 + 4 * s + 5)
zero_frequency_gain(g)
# output
0.2

the zero-frequency gain is the ratio of the steady state output value to the steady state input value (e.g., consider a step input). note that the zero-frequency gain could be infinite or zero, which is why we do not have a function to construct a transfer function from its zeros, poles, and zero-frequency gain.

poles, zeros, and zero-frequency gain of a transfer function

compute the poles, zeros, and zero-frequency gain of a transfer function all at once as follows:

g = (5 * s + 5) / (s ^ 2 + 4 * s + 5)
z, p, gain = zeros_poles_gain(g)
# output
([-1.0], ComplexF64[-2.0 - 1.0im, -2.0 + 1.0im], 1.0)

cancel poles and zeros

cancel pairs of identical poles and zeros in a transfer function as follows:

# define g(s) = s * (s+1) / ((s+3) * s * (s+1) ^ 2)
g = TransferFunction([1, 1, 0], [1, 5, 7, 3, 0])
pole_zero_cancellation(g) # 1 / ((s+3) * (s+1))
# output
         1.0
---------------------
1.0*s^2 + 4.0*s + 3.0

under the hood, pole_zero_cancellation compares all pairs of poles and zeros to look for identical pairs via isapprox. after removing identical pole-zero pairs, we reconstruct the transfer function from the remaining poles and zeros–-in addition to its k-factor. we ensure that the coefficients in the resulting rational function are real.

Note

pole-zero cancellation is done automatically when multiplying, dividing, adding, and subtracting transfer functions, as illustrated below.

g = s * (s+1) / ((s+3) * s * (s+1) ^ 2)
# output
         1.0
---------------------
1.0*s^2 + 4.0*s + 3.0

the order of a transfer function

we can find the apparent order of the polynomials in the numerator and denominator of the rational function comprising the transfer function:

g = (s + 1) / ((s + 2) * (s + 3))
system_order(g)
# output
(1, 2)

frequency response of an open-loop transfer function

in the closed loop below, $Y_{sp}$ is the set point for the output, $E$ is the error, and $Y_m$ is the measurement of the output.

compute the critical frequency, gain crossover frequency, gain margin, and phase margin of a closed loop control system with open-loop transfer function g_ol with gain_phase_margins. for example, consider:

\[g_{ol}(s)=\dfrac{2e^{-s}}{5s+1}\]

g_ol = 2 * exp(-s) / (5 * s + 1)
margins = gain_phase_margins(g_ol)
# output
-- gain/phase margin info--
	critical frequency ω_c [rad/time]:       1.68868
	gain crossover frequency ω_g [rad/time]: 0.34641
	gain margin:                             4.25121
	phase margin:                            1.74798

access the attributes of margins via:

margins.ω_c          # critical freq. (radians / time)
margins.ω_g          # gain crossover freq. (radians / time)
margins.gain_margin  # gain margin
margins.phase_margin # phase margin (radians)

special transfer functions

(0, 1) order transfer functions

\[g(s)=\frac{K}{\tau s +1}\]

easily construct:

K = 2.0
τ = 3.0
g = first_order_system(K, τ)
# output
    2.0
-----------
3.0*s + 1.0

compute time constant:

g = 10 / (6 * s + 2)
time_constant(g)
# output
3.0

(0, 2) order transfer functions

\[g(s)=\frac{K}{\tau^2 s^2 + 2\tau \xi s +1}\]

easily construct:

K = 1.0
τ = 2.0
ξ = 0.1
g = second_order_system(K, τ, ξ)
# output
         1.0
---------------------
4.0*s^2 + 0.4*s + 1.0

compute time constant, damping coefficient:

τ = time_constant(g)
# output
2.0
ξ = damping_coefficient(g)
# output
0.1

closed-loop transfer functions

to represent a closed-loop transfer function, we use a special transfer function type, ClosedLoopTransferFunction. this is only necessary when time delays are involved, but it works for when time delays are not involved as well.

using block diagram algebra, we find the closed-loop transfer functions that relate changes in the output $y$ to changes in the set point $y_{sp}$ and to changes in the disturbance $d$:

\[g_r(s)=\dfrac{Y(s)}{D(s)}=\dfrac{g_d(s)}{1+g_c(s)g_u(s)g_m(s)}\]

\[g_s(s)=\dfrac{Y(s)}{Y_{sp}(s)}=\dfrac{g_c(s)g_u(s)}{1+g_c(s)g_u(s)g_m(s)}\]

we construct these two closed-loop transfer functions as gr and gs as follows.

# PI controller transfer function
pic = PIController(1.0, 2.0)
gc = TransferFunction(pic)

# process, sensor dynamics
gu = 2 / (4 * s + 1) * exp(-0.5 * s)
gm = 1 / (s + 1) * exp(-0.1 * s)
gd = 6 / (6 * s + 1)

# open-loop transfer function
g_ol = gc * gu * gm

# closed-loop transfer function for regulator response
gr = ClosedLoopTransferFunction(gd, g_ol)
# output
closed-loop transfer function.
      top
    -------
    1 + g_ol

  top =
    6.0
-----------
6.0*s + 1.0

  g_ol =
       4.0*s + 2.0
-------------------------- e^(-0.6*s)
8.0*s^3 + 10.0*s^2 + 2.0*s
# closed-loop transfer function for servo response
gs = ClosedLoopTransferFunction(gc * gu, g_ol)
# output
closed-loop transfer function.
      top
    -------
    1 + g_ol

  top =
  4.0*s + 2.0
--------------- e^(-0.5*s)
8.0*s^2 + 2.0*s

  g_ol =
       4.0*s + 2.0
-------------------------- e^(-0.6*s)
8.0*s^3 + 10.0*s^2 + 2.0*s

detailed docs

Controlz.TransferFunctionType
tf = TransferFunction([1, 2], [3, 5, 8])
tf = TransferFunction([1, 2], [3, 5, 8], 3.0)

construct a transfer function representing a linear, time-invariant system.

example

to construct the transfer function

\[G(s) = \frac{4e^{-2.2s}}{2s+1}\]

in Julia:

tf = TransferFunction([4], [2, 1], 2.2)
# output
    4.0
----------- e^(-2.2*s)
2.0*s + 1.0

attributes

  • numerator::Polynomial{Float64, :s}: the polynomial in the numerator of the transfer function
  • denominator::Polynomial{Float64, :s}: the polynomial in the denominator of the transfer function
  • time_delay::Float64: the associated time delay
source
Controlz.ClosedLoopTransferFunctionType

a closed-loop transfer function that relates an output Y and an input U in a feedback loop.

the resulting closed-loop transfer function is:

 Y      top
--- = --------
 U    1 + g_ol

example

g_ol = 4 / (s + 1) * 2 / (s + 2)
top = 5 / (s + 4)
g = ClosedLoopTransferFunction(top, g_ol)
# output
closed-loop transfer function.
      top
    -------
    1 + g_ol

  top =
    5.0
-----------
1.0*s + 4.0

  g_ol =
         8.0
---------------------
1.0*s^2 + 3.0*s + 2.0

attributes

  • top::TransferFunction: numerator
  • g_ol::TransferFunction: open-loop transfer function
source
Controlz.zero_frequency_gainFunction
K = zero_frequency_gain(tf)

compute the (signed) zero frequency gain of a transfer function $g(s)$, which is:

\[K := \lim_{s\rightarrow 0} G(s)\]

the zero-frequency gain "represents the ratio of the steady state value of the output with respect to a step input" source

example

g = 5 / (3 * s + 1)
K = zero_frequency_gain(g)
# output
5.0

arguments

  • tf::TransferFunction: the transfer function

returns

  • K::Float64: the zero-frequency gain of the transfer function
source
Controlz.zeros_poles_gainFunction
z, p, gain = zeros_poles_gain(tf)

Compute the zeros, poles, and zero-frequency gain of a transfer function.

  • the zeros are the zeros of the numerator of the transfer function.
  • the poles are the zeros of the denominator of the transfer function.
  • the zero-frequency gain is the transfer function evaluated at $s=0$
source
Controlz.zeros_poles_kFunction
# compute the zeros, poles, and k-factor of a transfer function
z, p, k = zeros_poles_k(tf)
# construct a transfer function from its zeros, poles, and k-factor
tf = zeros_poles_k(z, p, k, time_delay=0.0)

the representation of a transfer function in this context is:

\[g(s)=k\dfrac{\Pi_j (s-z_j)}{\Pi_j (s-p_j)}\]

where $z_j$ is zero $j$, $p_j$ is pole $j$, and $k$ is a constant factor (not equal to the zero-frequency gain) that uniquely specifies the transfer function.

  • the zeros are the zeros of the numerator of the transfer function.
  • the poles are the zeros of the denominator of the transfer function.
source
Controlz.pole_zero_cancellationFunction
tf = pole_zero_cancellation(tf, verbose=false, digits=8)

find (pole, zero) pairs such that pole = zero and return a new transfer function with those pairs cancelled. this is achieved by comparing the poles and zeros with isapprox, with poles and zeros rounded to digits digits (also applies to reconstruction).

arguments

  • tf::TransferFunction: the transfer function
  • verbose::Bool=false: print off which poles, zeros are cancelled.
  • digits::Int: number of digits to round poles and zeros to, for (i) cancelling and (ii) reconstruction.

example

pole_zero_cancellation(s * (s - 1) / (s * (s + 1)))
# output
1.0*s - 1.0
-----------
1.0*s + 1.0
source
Controlz.evaluateFunction
evaluate(tf, z)

evaluate a TransferFunction, tf, at a particular number z (could be complex).

example

tf = TransferFunction([1], [3, 1])
evaluate(tf, 1.0)
# output
0.25
source
Controlz.properFunction
proper(tf)

Return true if transfer function tf is proper and false otherwise.

source
Controlz.characteristic_polynomialFunction
p = characteristic_polynomial(g_ol)

Determine the characteristic polynomial associated with open loop transfer function g_ol.

The characteristic polynomial is $1+g_{ol}(s)$. The roots of the characteristic polynomial determine the character of the response of the closed loop system to bounded inputs.

Arguments

  • g_ol::TransferFunction: open loop transfer function

Returns

a polynomial of type Polynomial

Example

g_ol = 4 / (s + 3) / (s + 2) / (s + 1)
characteristic_polynomial(g_ol)
# output
Polynomial(10.0 + 11.0*s + 6.0*s^2 + 1.0*s^3)
source
Controlz.zpk_formFunction
tf = zpk_form(tf)

write transfer function tf in zeros, poles, k-factor form:

\[g(s)=k\dfrac{\Pi_j (s-z_j)}{\Pi_j (s-p_j)}\]

where $z_j$ is zero $j$, $p_j$ is pole $j$, and $k$ is a constant factor (not equal to the zero-frequency gain) that uniquely specifies the transfer function.

this is achieved by multiplying by 1.0 in a fancy way such that the highest power of $s$ in the denominator is associated with a coefficient of $1$.

Example

g = 8.0 / (2 * s^2 + 3 * s + 4)
g_zpk = zpk_form(g)
# output
         4.0
---------------------
1.0*s^2 + 1.5*s + 2.0
source
Controlz.system_orderFunction
o = system_order(tf::TransferFunction)

return the order of the numerator and denominator of the transfer function tf.

use pole_zero_cancellation first if you wish to cancel poles and zeros that are equal before determining the order.

returns

o::Tuple{Int, Int}: (order of numerator, order of denominator)

examples

g = 1 / (s + 1)
system_order(g)
# output 
(0, 1)

g = (s + 1) / ((s + 2) * (s + 3))
system_order(g)
# output
(1, 2)
source
Controlz.first_order_systemFunction
g = first_order_system(K, τ)

construct a first-order transfer function with gain K and time constant τ:

\[g(s)=\frac{K}{\tau s+1}\]

example

K = 1.0
τ = 3.0
g = first_order_system(K, τ)
# output
    1.0
-----------
3.0*s + 1.0

returns

  • g::TransferFunction: the first order transfer function. well, (0, 1) order.
source
Controlz.second_order_systemFunction
g = second_order_system(K, τ, ξ)

construct a second-order transfer function with gain K, time constant τ, and damping coefficient ξ:

\[g(s)=\frac{K}{\tau^2 s^2 + 2\tau \xi s +1}\]

example

K = 1.0
τ = 2.0
ξ = 0.1
g = second_order_system(K, τ, ξ)
# output
         1.0
---------------------
4.0*s^2 + 0.4*s + 1.0

returns

  • g::TransferFunction: the second order transfer function. well, (0, 2) order.
source
Controlz.time_constantFunction
τ = time_constant(g)

compute the time constant τ of an order (0, 1) or order (0, 2) transfer function.

order (0, 1) representation:

\[g(s)=\frac{K}{\tau s+1}\]

order (0, 2) representation:

\[g(s)=\frac{K}{\tau^2 s^2 + 2\tau \xi s +1}\]

returns

τ::Float64: the time constant.

examples

g = 4 / (6 * s + 2)
time_constant(g)
# output 
3.0

g = 1.0 / (8 * s^2 + 0.8 * s + 2)
time_constant(g) 
# output
2.0
source
Controlz.damping_coefficientFunction
ξ = damping_coefficient(g)

compute the damping coefficient ξ of an order (0, 2) transfer function.

order (0, 2) representation:

\[g(s)=\frac{K}{\tau^2 s^2 + 2\tau \xi s +1}\]

returns

ξ::Float64: the damping coefficient

examples

g = 1.0 / (8 * s^2 + 0.8 * s + 2)
damping_coefficient(g)
# output
0.1
source
Controlz.gain_phase_marginsFunction
margins = gain_phase_margins(g_ol, ω_c_guess=0.001, ω_g_guess=0.001)

compute critical frequency (radians / time), gain crossover frequency (radians / time), gain margin, and phase margin (radians) of a closed loop, given its closed loop transfer function g_ol::TransferFunction.

if ωc or ωg is not found (i.e. if either are NaN), but the bode_plot clearly shows a critical/gain crossover frequency, adjust ω_c_guess or ω_g_guess to find the root.

Example

g_ol = 2 * exp(-s) / (5 * s + 1)
margins = gain_phase_margins(g_ol)
margins.ω_c # critical freq. (radians / time)
margins.ω_g # gain crossover freq. (radians / time)
margins.gain_margin # gain margin
margins.phase_margin # phase margin (radians)
source