Creating Systems
This page illustrates how to create system models such as transfer functions and statespace models. This topic is also treated in the introductory video below:
Transfer Functions
tf  Rational Representation
The basic syntax for creating a transfer function is tf
tf(num, den) # Continuoustime system
tf(num, den, Ts) # Discretetime system
where num
and den
are the polynomial coefficients of the numerator and denominator of the polynomial and Ts
, if provided, is the sample time for a discretetime system.
Example:
tf([1.0],[1,2,1])
# output
TransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}
1.0

1.0s^2 + 2.0s + 1.0
Continuoustime transfer function model
The transfer functions created using this method will be of type TransferFunction{SisoRational}
. For more general expressions, it is often more convenient to define s = tf("s")
:
Example:
julia> s = tf("s")
TransferFunction{Continuous,ControlSystems.SisoRational{Int64}}
s

1
Continuoustime transfer function model
This allows us to use s
to define transferfunctions:
julia> (s1)*(s^2 + s + 1)/(s^2 + 3s + 2)/(s+1)
TransferFunction{Continuous,ControlSystems.SisoRational{Int64}}
s^3  1

s^3 + 4*s^2 + 5*s + 2
Continuoustime transfer function model
zpk  PoleZeroGain Representation
Sometimes it's better to represent the transfer function by its poles, zeros and gain, this can be done using the function zpk
zpk(zeros, poles, gain) # Continuoustime system
zpk(zeros, poles, gain, Ts) # Discretetime system
where zeros
and poles
are Vectors
of the zeros and poles for the system and gain
is a gain coefficient.
Example
zpk([1.0,1], [5, 10], 2)
# output
TransferFunction{Continuous, ControlSystemsBase.SisoZpk{Float64, Float64}}
(1.0s + 1.0)(1.0s  1.0)
2.0
(1.0s + 5.0)(1.0s + 10.0)
Continuoustime transfer function model
The transfer functions created using this method will be of type TransferFunction{SisoZpk}
.
StateSpace Systems
A statespace system
\[\begin{aligned} \dot{x} &= Ax + Bu \\ y &= Cx + Du \end{aligned}\]
in continuous time, or
\[\begin{aligned} x_{t+T_s} &= Ax_t + Bu_t \\ y_t &= Cx_t + Du_t \end{aligned}\]
in discrete time, is created using
ss(A,B,C,D) # Continuoustime system
ss(A,B,C,D,Ts) # Discretetime system
and they behave similarly to transfer functions.
The ss
constructor allows you to
 Pass
0
instead of a $D$ matrix, and an appropriately sized zero matrix is created automatically.  Pass
I
instead of a $C$ matrix, and an appropriately sized identity matrix is created automatically. TheUniformScaling
operatorI
lives in theLinearAlgebra
standard library which must be loaded first.
Statespace systems with heterogeneous matrix types are also available, which can be used to create systems with static or sized matrices, e.g.,
using ControlSystemsBase, StaticArrays
sys = ss([5 0; 0 5],[2; 2],[3 3],[0])
HeteroStateSpace(sys, to_sized)
HeteroStateSpace(sys, to_static)
StaticStateSpace{Continuous, 1, 1, 2, StaticArraysCore.SMatrix{2, 2, Int64, 4}, StaticArraysCore.SMatrix{2, 1, Int64, 2}, StaticArraysCore.SMatrix{1, 2, Int64, 2}, StaticArraysCore.SMatrix{1, 1, Int64, 1}}
A =
5 0
0 5
B =
2
2
C =
3 3
D =
0
Continuoustime statespace model
Notice the different matrix types used.
To associate names with states, inputs and outputs, see named_ss
from RobustAndOptimalControl.jl.
Converting between types
It is sometime useful to convert one representation to another. This is possible using the constructors tf, zpk, ss
, for example
tf(zpk([1], [1], 2, 0.1))
# output
TransferFunction{Discrete{Float64}, ControlSystemsBase.SisoRational{Int64}}
2z + 2

z  1
Sample Time: 0.1 (seconds)
Discretetime transfer function model
Converting between continuous and discrete time
A continuoustime system represents differential equations or a transfer function in the Laplace domain, while a discretetime system represents difference equations or a transfer function in the Zdomain.
The functions c2d
and d2c
implement sampling/discretization of continuoustime systems and the inverse mapping from discretetime to continuoustime systems.
Delay Systems
The constructor delay
creates a pure delay, which may be connected to a system by multiplication:
delay(1.2) # Pure delay or 1.2s
tf(1, [1, 1])*delay(1.2) # Input delay
delay(1.2)*tf(1, [1, 1]) # Output delay
Delayed systems can also be created using
s = tf("s")
L = 1.2 # Delay time
tf(1, [1, 1]) * exp(L*s)
Padé approximations of delays can be created using pade
. Models with delays can be discretized using c2d
, currently, only delays that are integer multiples of the sample time are supported. Pure fractional delays can be approximately discretized using the function thiran
.
A tutorial on delay systems is available here:
Nonlinear Systems
Simplifying systems
A statespace system with a nonminimal realization, or a transfer function with overlapping zeros and poles, may be simplified using the function minreal
. Systems that are structurally singular, i.e., that contains outputs that can not be reached from the inputs based on analysis of the structure of the zeros in the system matrices only, can be simplified with the function sminreal
.
Examples:
julia> using ControlSystemsBase
julia> G = tf([1, 1], [1, 1])
TransferFunction{Continuous, ControlSystemsBase.SisoRational{Int64}} s + 1  s + 1 Continuoustime transfer function model
julia> minreal(G) # Performs polezero cancellation
TransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}} 1.0  1.0 Continuoustime transfer function model
julia> P = tf(1, [1, 1]) > ss
StateSpace{Continuous, Float64} A = 1.0 B = 1.0 C = 1.0 D = 0.0 Continuoustime statespace model
julia> G = P / (1 + P) # this creates a nonminimal realization, use feedback(P) instead
StateSpace{Continuous, Float64} A = 2.0 B = 1.4142135623730954 C = 0.7071067811865476 D = 0.0 Continuoustime statespace model
julia> feedback(P) # Creates a minimal realization directly
StateSpace{Continuous, Float64} A = 2.0 B = 1.0 C = 1.0 D = 0.0 Continuoustime statespace model
julia> Gmin = minreal(G) # this simplifies the realization to a minimal realization
StateSpace{Continuous, Float64} A = 2.0 B = 1.4142135623730954 C = 0.7071067811865476 D = 0.0 Continuoustime statespace model
julia> norm(Gmin  feedback(P), Inf) # No difference
1.110224134848181e16
julia> bodeplot([G, Gmin, feedback(P)]) # They are all identical
Plot{Plots.GRBackend() n=6}
Multiplying systems
Two systems can be connected in series by multiplication
using ControlSystemsBase
P1 = ss(1,1,1,0)
P2 = ss(2,1,1,0)
P2*P1
StateSpace{Continuous, Int64}
A =
2 1
0 1
B =
0
1
C =
1 0
D =
0
Continuoustime statespace model
The state of the resulting system is the concatenation of the states of the two systems, starting with the left/first operand (P2
above).
If the input dimension of P2
does not match the output dimension of P1
, an error is thrown. If one of the systems is SISO and the other is MIMO, broadcasted multiplication will expand the SISO system to match the input or output dimension of the MIMO system, e.g.,
Pmimo = ssrand(2,2,1)
Psiso = ss(2,1,1,0)
# Psiso * Pmimo # error
Psiso .* Pmimo ≈ [Psiso 0; 0 Psiso] * Pmimo # Broadcasted multiplication expands SISO into diagonal system
true
Broadcasted multiplication between a system and an array is only allowed for diagonal arrays
using LinearAlgebra
Psiso .* I(2)
StateSpace{Continuous, Int64}
A =
2 0
0 2
B =
1 0
0 1
C =
1 0
0 1
D =
0 0
0 0
Continuoustime statespace model
Adding systems
Two systems can be connected in parallel by addition
P12 = P1 + P2
StateSpace{Continuous, Int64}
A =
1 0
0 2
B =
1
1
C =
1 1
D =
0
Continuoustime statespace model
The state of the resulting system is the concatenation of the states of the two systems, starting with the left/first operand (P1
above).
MIMO systems and arrays of systems
Concatenation of systems creates MIMO systems, which is different from an array of systems. For example
using ControlSystemsBase
P = ss(1,1,1,0)
P_MIMO = [P 2P]
StateSpace{Continuous, Int64}
A =
1 0
0 1
B =
1 0
0 1
C =
1 2
D =
0 0
Continuoustime statespace model
is a 1×2 MISO system, not a 1×2 array.
From SISO to MIMO
SISO systems do not multiply MIMO systems directly, i.e.,
using Test
siso = ss(1,1,1,0)
mimo = ssrand(2,2,2)
@test_throws DimensionMismatch siso * mimo
Test Passed
Thrown: DimensionMismatch
To multiply siso
with each output channel of mimo
in the example above, use broadcasting:
siso .* mimo
StateSpace{Continuous, Float64}
A =
1.0 0.0 0.814115710635757 2.05611954192475
0.0 1.0 0.309671182140287 0.8213151945608457
0.0 0.0 0.4615064500828379 2.3246447006361297
0.0 0.0 1.214054438269907 0.40767445019951687
B =
0.4465259344260694 0.8557846776610339
1.074561991927812 1.898978885310723
2.7521659903912368 0.2210180097529923
1.1205600313664303 1.2754295013740533
C =
1.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0
D =
0.0 0.0
0.0 0.0
Continuoustime statespace model
This is equivalent to first expanding the SISO system into a diagonal system
using LinearAlgebra
(siso .* I(2)) * mimo
StateSpace{Continuous, Float64}
A =
1.0 0.0 0.814115710635757 2.05611954192475
0.0 1.0 0.309671182140287 0.8213151945608457
0.0 0.0 0.4615064500828379 2.3246447006361297
0.0 0.0 1.214054438269907 0.40767445019951687
B =
0.4465259344260694 0.8557846776610339
1.074561991927812 1.898978885310723
2.7521659903912368 0.2210180097529923
1.1205600313664303 1.2754295013740533
C =
1.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0
D =
0.0 0.0
0.0 0.0
Continuoustime statespace model
Converting an array of systems to a MIMO system
Diagonal MIMO systems can be created from a vector of systems using append
P1 = ssrand(1,1,1)
P2 = ssrand(1,1,1)
append(P1, P2)
StateSpace{Continuous, Float64}
A =
0.6071761154690204 0.0
0.0 0.22966324841010835
B =
0.31855392586848136 0.0
0.0 0.3665955381662163
C =
0.5776702975692982 0.0
0.0 0.48894241370063657
D =
0.7519721567302424 0.0
0.0 0.12899827293248062
Continuoustime statespace model
More general arrays of systems can be converted to a MIMO system using array2mimo
.
sys_array = fill(P, 2, 2) # Creates an array of systems
mimo_sys = array2mimo(sys_array)
StateSpace{Continuous, Int64}
A =
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
B =
1 0
0 1
1 0
0 1
C =
1 1 0 0
0 0 1 1
D =
0 0
0 0
Continuoustime statespace model
Converting MIMO system to an array of systems
This conversion is not explicitly supported, but is easy enough to accomplish with standard Julia code, for example:
P = ssrand(2,3,1) # A random 2×3 MIMO system
sys_array = getindex.(Ref(P), 1:P.ny, (1:P.nu)')
2×3 Matrix{StateSpace{Continuous, Float64}}:
StateSpace{Continuous, Float64}
A =
0.08691007291327735
B =
0.5806774863172849
C =
0.5737119614126563
D =
1.0096819268508332
Continuoustime statespace model … StateSpace{Continuous, Float64}
A =
0.08691007291327735
B =
1.324518848959261
C =
0.5737119614126563
D =
1.0462125553246147
Continuoustime statespace model
StateSpace{Continuous, Float64}
A =
0.08691007291327735
B =
0.5806774863172849
C =
0.012172225030040618
D =
0.86166223315889
Continuoustime statespace model StateSpace{Continuous, Float64}
A =
0.08691007291327735
B =
1.324518848959261
C =
0.012172225030040618
D =
0.26750937193882823
Continuoustime statespace model
Creating arrays with different types of systems
When calling hcat/vcat
, Julia automatically tries to promote the types to the smallest common supertype, this means that creating an array with one continuous and one discretetime system fails
P_cont = ssrand(2,3,1)
P_disc = ssrand(2,3,1, Ts=1)
@test_throws ErrorException [P_cont, P_disc] # ERROR: Sampling time mismatch
Test Passed
Thrown: ErrorException
You can explicitly tell Julia that you want a particular supertype, e.g,
StateSpace[P_cont, P_disc]
2element Vector{StateSpace}:
StateSpace{Continuous, Float64}
A =
0.06682087565482409
B =
1.4946819804967477 1.7753277533475647 0.06871071737381562
C =
1.1058131091255579
1.4554082338585987
D =
0.3690864860323875 0.38877368689346825 0.5381864459037952
0.9468404821940324 0.18773341014198228 0.37455239368384247
Continuoustime statespace model
StateSpace{Discrete{Int64}, Float64}
A =
0.9
B =
0.8947598765146301 2.824305648773669 1.8515305564795363
C =
0.9997315444062458
0.2682635474824611
D =
0.7792688578785669 0.4184642057104451 1.3735905782913203
1.320275850416764 0.5373239855237092 0.6193200509609528
Sample Time: 1 (seconds)
Discretetime statespace model
The type StateSpace
is abstract, since the type parameters are not specified.
Demo systems
The module ControlSystemsBase.DemoSystems
contains a number of demo systems demonstrating different kinds of dynamics.
From block diagrams to code
This section lists a number of block diagrams, and indicates the corresponding transfer functions and how they are built in code.
The function feedback(G1, G2)
can be thought of like this: the first argument G1
is the system that appears directly between the input and the output (the forward path), while the second argument G2
(defaults to 1 if omitted) contains all other systems that appear in the closed loop (the feedback path). The feedback is assumed to be negative, unless the argument pos_feedback = true
is passed (lft
is an exception, which due to convention defaults to positive feedback). This means that feedback(G, 1)
results in unit negative feedback, while feedback(G, 1)
or feedback(G, 1, pos_feedback = true)
results in unit positive feedback.
The returned closedloop system will have a state vector comprised of the state of G1
followed by the state of G2
.
Closedloop system from reference to output
r ┌─────┐ ┌─────┐
───►│ │ u │ │ y
│ C ├────►│ P ├─┬─►
┌►│ │ │ │ │
│ └─────┘ └─────┘ │
│ │
└─────────────────────┘
\[Y = \dfrac{PC}{I+PC}R\]
Code: feedback(P*C)
or equivalently comp_sensitivity(P, C)
. Here, the system $PC$ appears directly between the input $r$ and the output $y$, and the feedback loop is negative identity. We thus call feedback(P*C) = feedback(P*C, 1)
d ┌───┐ y
───+─►│ P ├─┬───►
▲ └───┘ │
│ │
│ ┌───┐ │
└──┤ C │◄┘
└───┘
\[Y = \dfrac{P}{I+PC}D = PSD\]
Code: feedback(P, C)
or equivalently G_PS(P, C)
. Here, only $P$ appears directly between $d$ and $y$, while $C$ appears first in the feedback loop. We thus call feedback(P, C)
Sensitivity function at plant input
d e┌───┐
───+─►│ P ├─┬───►
▲ └───┘ │
│ │
│ ┌───┐ │
└──┤ C │◄┘
└───┘
\[E = \dfrac{1}{I+CP}D = SD\]
Code: feedback(1, C*P)
or equivalently input_sensitivity(P, C)
. Here, there are no systems directly between the input and the output, we thus call feedback(1, C*P)
. Note the order in C*P
, which is important for MIMO systems. This computes the sensitivity function at the plant input. It's more common to analyze the sensitivity function at the plant output, illustrated below (for SISO systems they are equivalent).
Sensitivity function at plant output
┌───┐
───+─►│ P ├─+◄── e
▲ └───┘ │
│ │y
│ ┌───┐ │
└──┤ C │◄┘
└───┘
\[Y = \dfrac{1}{I+PC}E = SE\]
Code: feedback(1, P*C)
or equivalently output_sensitivity(P, C)
. Note the reverse order in $PC$ compared to the input sensitivity function above.
Reference $r$ and input disturbance $d$ to output $y$ and control signal $u$. This example forms the transfer function matrix with $r$ and $d$ as inputs, and $y$ and $u$ as outputs.
d
┌─────┐ │ ┌─────┐
r │ │u ▼ │ │ y
──+─►│ C ├──+─►│ P ├─┬─►
▲ │ │ │ │ │
│ └─────┘ └─────┘ │
│ │
└──────────────────────┘
\[\begin{bmatrix} y \\ u \end{bmatrix} = \begin{bmatrix} \dfrac{PC}{I + PC} & \dfrac{C}{I + PC} \\ \dfrac{P}{I + PC} & \dfrac{PC}{I + PC} \end{bmatrix} \begin{bmatrix} r \\ d \end{bmatrix}\]
Code: feedback(C, P, W2=:, Z2=:, Zperm=[(1:P.ny).+P.nu; 1:P.nu]) # y,u from r,d
. Here, we have reversed the order of P
and C
to get the correct sign of the control signal. We also make use of the keyword arguments W2
and Z2
to specify that we want to include the inputs and outputs of P
as external inputs and outputs, and Zperm
to specify the order of the outputs ($y$ before $u$).
Linear fractional transformation
┌─────────┐
z◄───┤ │◄────w
│ P │
y┌───┤ │◄───┐u
│ └─────────┘ │
│ │
│ ┌───┐ │
│ │ │ │
└─────►│ K ├───────┘
│ │
└───┘
\[Z = \operatorname{lft}{(P, K)} W\]
Code: lft(P, K)
z1 z2
▲ ┌─────┐ ▲ ┌─────┐
│ │ │ │ │ │
w1──+─┴─►│ C ├──┴───+─►│ P ├─┐
│ │ │ │ │ │ │
│ └─────┘ │ └─────┘ │
│ w2 │
└────────────────────────────┘
The transfer function from $w_1, w_2$ to $z_1, z_2$ contains all the transfer functions that are commonly called "gang of four" (see also gangoffour
).
\[\begin{bmatrix} z_1 \\ z_2 \end{bmatrix} = \begin{bmatrix} I \\ C \end{bmatrix} (I + PC)^{1} \begin{bmatrix} I & P \end{bmatrix} \begin{bmatrix} w_1 \\ w_2 \end{bmatrix}\]
Code: This function requires the package RobustAndOptimalControl.jl.
RobustAndOptimalControl.extended_gangoffour(P, C, pos=true)
# For SISO P
S = G[1, 1]
PS = G[1, 2]
CS = G[2, 1]
T = G[2, 2]
# For MIMO P
S = G[1:P.ny, 1:P.nu]
PS = G[1:P.ny, P.nu+1:end]
CS = G[P.ny+1:end, 1:P.nu]
T = G[P.ny+1:end, P.nu+1:end]
See also
output_sensitivity
input_sensitivity
output_comp_sensitivity
input_comp_sensitivity
G_PS
G_CS
gangoffour
)gangoffourplot
)
This diagram is more complicated and forms several connections, including both feedforward and feedback connections. A code file that goes through how to form such complicated connections using named signals is linked below. This example uses the package RobustAndOptimalControl.jl.
yF
┌────────────────────────────────┐
│ │
┌───────┐ │ ┌───────┐ yR ┌─────────┐ │ ┌───────┐
uF │ │ │ │ ├──────► │ yC │ uP│ │ yP
────► F ├─┴──► R │ │ C ├────+────► P ├────┬────►
│ │ │ │ ┌──► │ │ │ │
└───────┘ └───────┘ │ └─────────┘ └───────┘ │
│ │
└───────────────────────────────────┘
See code example complicated_feedback.jl.
Filter design
Filters can be designed using DSP.jl. This results in filter objects with types from the DSP package, which can be converted to transfer functions using tf
from ControlSystemsBase.
using DSP, ControlSystemsBase, Plots
fs = 100
df = digitalfilter(Bandpass(5, 10; fs), Butterworth(2))
G = tf(df, 1/fs) # Sample time must be provided in the conversion to get the correct frequency scale in the Bode plot
bodeplot(G, xscale=:identity, yscale=:identity, hz=true)
See also