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.TransferFunction
— Typetf = 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 functiondenominator::Poly
: the polynomial in the denominator of the transfer functiontime_delay::Float64
: the associated time delay
Controlz.zero_frequency_gain
— FunctionK = 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
Controlz.zeros_poles_gain
— Functionz, 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$
Controlz.zeros_poles_k
— Function# 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.
Controlz.pole_zero_cancellation
— Functiontf = 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 functionverbose::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)
Controlz.evaluate
— Functionevaluate(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
Controlz.proper
— Functionproper(tf)
Return true
if transfer function tf
is proper and false
otherwise.
Controlz.strictly_proper
— Functionstrictly_proper(tf)
Return true
if transfer function tf
is strictly proper and false
otherwise.
Controlz.characteristic_polynomial
— Functionp = 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`
Controlz.zpk_form
— Functiontf = 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)
Controlz.system_order
— Functiono = 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) ```
Controlz.first_order_system
— Functiong = 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.
Controlz.second_order_system
— Functiong = 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.
Controlz.time_constant
— Functionτ = 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
Controlz.damping_coefficient
— Functionξ = 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
Controlz.gain_phase_margins
— Functionmargins = 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)