transfer functions

the response [output $Y(s)$] of a linear, time-invariant system to any input [$U(s)$] is characterized by a transfer function $g(s)=Y(s)/U(s)$.

constructing a transfer function

we use a data structure, TransferFunction, to represent a transfer function. for example, consider the transfer function $g(s)=\dfrac{5s+1}{s^2 + 4s+5}$.

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

g = (5 * s + 1) / (s ^ 2 + 4 * s + 5) # way 1

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 $g(s)$. The coefficients of the highest powers of $s$ go first.

g = TransferFunction([5, 1], [1, 4, 5]) # way 2

note that, under the hood, we defined s such that s == TransferFunction([1, 0], [1]).

as rational functions associated with a time delay, each TransferFunction data structure has a numerator (a polynomial in :s), denominator (a polynomial in :s), and time_delay (a real number) attribute. access these attributes as follows:

g.numerator   # 5s + 1, a `Poly`
g.denominator # s² + 4s + 5, a `Poly`
g.time_delay  # 0.0, a `Float64`

g.numerator and g.denominator are Poly types from the package 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)    # way 1
g = TransferFunction([3], [2, 1], θ) # way 2

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:

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

construting 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)  # way 3

the 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)
zeros_poles_k(g) # [-0.2], [-2-im, -2+im], 5

transfer function algebra

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

g₁ = 3 / (s + 2)
g₂ = 1 / (s + 4)

g_product = g₁ * g₂ # 3 / (s^2 + 6s + 8)

g_sum = g₁ + g₂     # (4s + 14) / (s^2 + 6s + 8)

evaluate a transfer function at a complex number

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

g = 4 / (s + 2)
evaluate(g, 2 * im) # 1 - im

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) # 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)
# z = [-1.0]
# p = [-2-im, -2+im]
# gain = 1.0

cancel poles and zeros

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

g = s * (s+1) / ((s+3) * s * (s+1) ^ 2)
pole_zero_cancellation(g) # 1 / ((s+3) * (s+1))

note that this cancellation is not done automatically.

under the hood, we compare 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, zeros, and k-factor. we ensure that the coefficients in the resulting rational function are real.

the order of a transfer function

we can find the 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) # (1, 2)

note that is only the apparent order; you may need to call pole_zero_cancellation to get the effective order:

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

frequency response of an open-loop transfer function

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)

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, τ) # 2 / (3 * s + 1)

compute time constant:

time_constant(10 / (6 * s + 2)) # 3

(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, τ, ξ) # 1 / (4 * s^2 + 0.4 * s + 1)

compute time constant, damping coefficient:

g = 1.0 / (8 * s^2 + 0.8 * s + 2)
τ = time_constant(g) # 2.0
ξ = damping_coefficient(g) # 0.1

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

construct the transfer function:

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

tf = TransferFunction([4], [2, 1], 2.2)

Attributes

  • numerator::Poly: the polynomial in the numerator of the transfer function
  • denominator::Poly: the polynomial in the denominator of the transfer function
  • time_delay::Float64: the associated time delay
source
Controlz.zero_frequency_gainFunction
K = zero_frequency_gain(tf)

Compute the (signed) zero frequency gain of a transfer function $g(s)$, which is the value of the transfer function at $s=0$. "It represents the ratio of the steady state value of the output with respect to a step input" source

Arguments

  • tf::TransferFunction: 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)

Find pairs of identical poles and zeros and return a new transfer function with the appropriate poles and zeros cancelled. This is achieved by comparing the poles and zeros with isapprox.

Arguments

  • tf::TransferFunction: the transfer function
  • verbose::Bool=false: print off which poles, zeros are cancelled.

Example

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

Evaluate a TransferFunction, tf, at a particular number z.

Examples

tf = TransferFunction([1], [3, 1])
evaluate(tf, 1.0) # 0.25
evaluate(tf, 2.0 + 3.0im) # also takes imaginary numbers as input
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 Poly

Example

g_ol = 4 / (s + 3) / (s + 2) / (s + 1)
characteristic_polynomial(g_ol) # s³ + 6s² + 11s + 10, a `Poly`
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) # 4 / (s^2 + 1.5 s + 2)
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) # (0, 1)

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

where pole_zero_cancellation is necessary: ``julia g = (s + 1) / (s + 1) ^ 2 system_order(g) # (1, 2)

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

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, τ) # 1 / (3 * s + 1)

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, τ, ξ) # 1 / (4 * s^2 + 0.4 * s + 1)

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) # 3.0

g = 1.0 / (8 * s^2 + 0.8 * s + 2)
time_constant(g) # 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) # 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