Создание систем
Передаточные функции
tf — рациональное представление
Синтаксис для создания передаточной функции с помощью tf имеет следующий вид.
tf(num, den)     # Система с непрерывным временем
tf(num, den, Ts) # Система с дискретным временемЗдесь num и den — это полиномиальные коэффициенты числителя и знаменателя многочлена, а Ts (если указано) — интервал дискретизации для системы с дискретным временем.
zpk — представление полюсов, нулей и усиления
Иногда передаточную функцию удобнее представлять в виде ее полюсов, нулей и коэффициента усиления. Для этого служит функция zpk.
zpk(zeros, poles, gain)     # Система с непрерывным временем
zpk(zeros, poles, gain, Ts) # Система с дискретным временемЗдесь zeros и poles — это векторы (Vectors) нулей и полюсов системы, а gain — коэффициент усиления.
Пример
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)
Continuous-time transfer function modelСоздаваемые таким способом передаточные функции имеют тип TransferFunction{SisoZpk}.
Системы пространства состояний
Система пространства состояний создается следующим образом:
ss(A,B,C,D)    # Система с непрерывным временем
ss(A,B,C,D,Ts) # Система с дискретным временемРаботать с такой системой можно так же, как с передаточными функциями.
Конструктор ss позволяет делать следующее:
- 
передавать 0вместо матрицы ; нулевая матрица соответствующего размера создается автоматически;
- 
передавать Iвместо матрицы ; матрица тождественности соответствующего размера создается автоматически. ОператорIUniformScalingпредоставляется стандартной библиотекойLinearAlgebra, которую нужно предварительно загрузить.
Также доступны системы пространства состояний с неоднородными типами матриц, которые можно использовать для создания систем со статическими или размерными матрицами, например:
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
Continuous-time state-space modelОбратите внимание на использование матриц различных типов.
Сведения о связывании имен с состояниями, входами и выходами см. в описании named_ss из пакета RobustAndOptimalControl.jl.
Преобразование типов
Иногда бывает полезно выполнить преобразование из одного представления в другое. Это можно сделать с помощью конструкторов tf, zpk, ss, например:
tf(zpk([-1], [1], 2, 0.1))
# output
TransferFunction{Discrete{Float64}, ControlSystemsBase.SisoRational{Int64}}
2z + 2
------
z - 1
Sample Time: 0.1 (seconds)
Discrete-time transfer function modelСистемы с запаздыванием
Конструктор delay создает чистое запаздывание, которое можно ввести в систему путем умножения:
delay(1.2)               # Чистое запаздывание или 1,2 с
tf(1, [1, 1])*delay(1.2) # Запаздывание входа
delay(1.2)*tf(1, [1, 1]) # Запаздывание выходаСистемы с запаздыванием также можно создавать следующим образом:
s = tf("s")
L = 1.2 # Время запаздывания
tf(1, [1, 1]) * exp(-L*s)Аппроксимацию Паде для запаздывания можно создать с помощью функции pade.
Руководство по системам с запаздыванием доступно здесь:
Нелинейные системы
См. раздел Nonlinear functionality.
Упрощение систем
Систему пространства состояний с неминимальной реализацией или передаточную функцию с перекрывающимися нулями и полюсами можно упростить с помощью функции minreal. Системы, которые являются структурно сингулярными, то есть выходов которых нельзя достичь от входов на основе лишь анализа структуры нулей в матрицах системы, можно упростить с помощью функции sminreal.
Примеры:
julia> using ControlSystemsBase
julia> G = tf([1, 1], [1, 1])
TransferFunction{Continuous, ControlSystemsBase.SisoRational{Int64}}
s + 1
-----
s + 1
Continuous-time transfer function model
julia> minreal(G) # Производит устранение нулевых значений сигнала
TransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}
1.0
---
1.0
Continuous-time 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
Continuous-time state-space model
julia> G = P / (1 + P) # здесь создается неминимальная реализация, поэтому используем вместо этого feedback(P)
StateSpace{Continuous, Float64}
A =
 -1.0  -1.0
  0.0  -2.0
B =
 1.0
 1.0
C =
 1.0  0.0
D =
 0.0
Continuous-time state-space model
julia> feedback(P) # Создает минимальную реализацию напрямую
StateSpace{Continuous, Float64}
A =
 -2.0
B =
 1.0
C =
 1.0
D =
 0.0
Continuous-time state-space model
julia> Gmin = minreal(G) # таким образом реализация упрощается до минимальной
StateSpace{Continuous, Float64}
A =
 -1.9999999999999993
B =
 -1.4142135623730951
C =
 -0.7071067811865472
D =
 0.0
Continuous-time state-space model
julia> norm(Gmin - feedback(P), Inf) # Нет отличий
1.110224134848181e-16
julia> bodeplot([G, Gmin, feedback(P)]) # Все одинаково
Plot{Plots.GRBackend() n=6}Умножение систем
Две системы можно соединить последовательно путем умножения.
using ControlSystemsBase
P1 = ss(-1,1,1,0)
P2 = ss(-2,1,1,0)
P2*P1StateSpace{Continuous, Int64}
A =
 -2   1
  0  -1
B =
 0
 1
C =
 1  0
D =
 0
Continuous-time state-space modelЕсли входное измерение P2 не соответствует выходному измерению P1, выдается ошибка. Если одна из систем одномерная, а другая — двухмерная, транслированное умножение расширяет одномерную систему так, чтобы она соответствовала входному или выходному измерению многомерной, например:
Pmimo = ssrand(2,2,1)
Psiso = ss(-2,1,1,0)
# Psiso * Pmimo # ошибка
Psiso .* Pmimo ≈ [Psiso 0; 0 Psiso] * Pmimo # Транслированное умножение расширяет одномерную систему до диагональнойtrueТранслированное умножение системы и массива допустимо только для диагональных массивов.
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
Continuous-time state-space modelМногомерные системы и массивы систем
В результате конкатенации систем образуется многомерная система, которая отличается от массива систем. Например:
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
Continuous-time state-space modelэто многомерная система 1×2, а не массив 1×2.
Из одномерной системы в многомерную
Одномерные системы не умножаются на многомерные напрямую:
using Test
siso = ss(-1,1,1,0)
mimo = ssrand(2,2,2)
@test_throws DimensionMismatch siso * mimoTest Passed
      Thrown: DimensionMismatchЧтобы в приведенном выше примере умножить siso на каждый выходной канал mimo, используйте трансляцию:
siso .* mimoStateSpace{Continuous, Float64}
A =
 -1.0   0.0   0.6480001475444149  -0.5837277961193962
  0.0  -1.0  -1.7039316164712754   1.5401479520865413
  0.0   0.0  -2.115494351059544   -1.8761537345884445
  0.0   0.0  -1.3413422781604374  -1.4565866982414075
B =
 -0.9508832623720646   -1.5870294046946989
  0.37005783730851793   1.069217230146147
 -0.04176136936679013   0.1320280925972221
  0.15655430568685796  -1.2664162819294336
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
Continuous-time state-space modelЭто равносильно предварительному развертыванию одномерной системы в диагональную.
using LinearAlgebra
(siso .* I(2)) * mimoStateSpace{Continuous, Float64}
A =
 -1.0   0.0   0.6480001475444149  -0.5837277961193962
  0.0  -1.0  -1.7039316164712754   1.5401479520865413
  0.0   0.0  -2.115494351059544   -1.8761537345884445
  0.0   0.0  -1.3413422781604374  -1.4565866982414075
B =
 -0.9508832623720646   -1.5870294046946989
  0.37005783730851793   1.069217230146147
 -0.04176136936679013   0.1320280925972221
  0.15655430568685796  -1.2664162819294336
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
Continuous-time state-space modelПреобразование массива систем в многомерную систему
Диагональную многомерную систему можно создать из вектора систем с помощью функции append.
P1 = ssrand(1,1,1)
P2 = ssrand(1,1,1)
append(P1, P2)StateSpace{Continuous, Float64}
A =
 -0.031445085752152435   0.0
  0.0                   -0.11431097869771967
B =
 -0.6661593733640553   0.0
  0.0                 -0.2871155475506572
C =
 1.2299213185896871   0.0
 0.0                 -0.5861950130060937
D =
 -1.7142289926543897  0.0
  0.0                 2.0816093031690217
Continuous-time state-space modelБолее общие массивы систем можно преобразовывать в многомерные системы с помощью метода array2mimo.
sys_array = fill(P, 2, 2) # Создает массив систем
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
Continuous-time state-space modelПреобразование многомерной системы в массив систем
Такое преобразование не поддерживается явным образом, но достаточно легко реализуется с помощью обычного кода Julia, например:
P = ssrand(2,3,1) # Случайная многомерная система 2×3
sys_array = getindex.(Ref(P), 1:P.ny, (1:P.nu)')2×3 Matrix{StateSpace{Continuous, Float64}}:
 StateSpace{Continuous, Float64}
A =
 -0.9561216469448534
B =
 -0.060187565566125686
C =
 -0.8266381980246545
D =
 1.1416543199147604
Continuous-time state-space model   …  StateSpace{Continuous, Float64}
A =
 -0.9561216469448534
B =
 -0.8979659719080596
C =
 -0.8266381980246545
D =
 -0.41416330898904435
Continuous-time state-space model
 StateSpace{Continuous, Float64}
A =
 -0.9561216469448534
B =
 -0.060187565566125686
C =
 -0.9174184153688885
D =
 -1.5772540039273821
Continuous-time state-space model     StateSpace{Continuous, Float64}
A =
 -0.9561216469448534
B =
 -0.8979659719080596
C =
 -0.9174184153688885
D =
 -0.3751748796599928
Continuous-time state-space modelСоздание массивов с различными типами систем
При вызове hcat/vcat Julia пытается автоматически продвинуть типы до наименьшего общего супертипа, то есть создать массив с одной непрерывной и одной дискретной системами не удастся.
P_cont = ssrand(2,3,1)
P_disc = ssrand(2,3,1, Ts=1)
@test_throws ErrorException [P_cont, P_disc] # ERROR: Sampling time mismatchTest Passed
      Thrown: ErrorExceptionВы можете явным образом сообщить Julia нужный супертип, например:
StateSpace[P_cont, P_disc]2-element Vector{StateSpace}:
 StateSpace{Continuous, Float64}
A =
 -1.3716716788894652
B =
 -0.3624875727629689  0.9644915820182179  0.6987358102174509
C =
  0.8623674722193923
 -0.2178013242366765
D =
 0.5138344957212424   -0.054813542456689734  -1.5355805182590119
 0.09198259556814194   1.2387723604810115    -0.09329636744953426
Continuous-time state-space model
 StateSpace{Discrete{Int64}, Float64}
A =
 0.9
B =
 0.310562326219272  -1.5299520100783581  0.6793697708186918
C =
 -0.3827715076233991
  0.3127849910381858
D =
 -0.9475730778435572   0.025282874604404964  -0.11929165323769904
 -0.34601203395549096  0.18186138919749933    0.7078840093774816
Sample Time: 1 (seconds)
Discrete-time state-space modelТип StateSpace является абстрактным, так как его параметры не заданы.
От блок-схем к коду
В этом разделе приводится ряд блок-схем с соответствующими передаточными функциями и показано, как они реализуются в коде.
Функцию feedback(G1, G2) можно представить так: первый аргумент G1 — это система, которая находится непосредственно между входом и выходом (прямой путь), а второй аргумент G2 (по умолчанию 1, если он не указан) содержит все остальные системы, из которых состоит замкнутый контур (путь обратной связи). Предполагается, что обратная связь отрицательная, если не передан аргумент pos_feedback = true (исключением является функция lft, которая согласно соглашению по умолчанию обладает положительной обратной связью). Это означает, что feedback(G, 1) даст отрицательную единичную обратную связь, а feedback(G, -1) или feedback(G, 1, pos_feedback = true) — положительную.
Замкнутая система от опорного узла к выходу
r   ┌─────┐     ┌─────┐
───►│     │  u  │     │ y
    │  C  ├────►│  P  ├─┬─►
 -┌►│     │     │     │ │
  │ └─────┘     └─────┘ │
  │                     │
  └─────────────────────┘
Код: feedback(P*C) или, что то же самое, comp_sensitivity(P, C). В данном случае система    находится непосредственно между входом    и выходом   , а контур обратной связи представляет собой отрицательное тождество. Поэтому мы вызываем feedback(P*C) = feedback(P*C, 1).
d     ┌───┐   y
───+─►│ P ├─┬───►
  -▲  └───┘ │
   │        │
   │  ┌───┐ │
   └──┤ C │◄┘
      └───┘
Код: feedback(P, C) или, что то же самое, G_PS(P, C). В данном случае только    находится непосредственно между    а   , в то время как    находится на первом месте в контуре обратной связи. Поэтому мы вызываем feedback(P, C).
Функция чувствительности на входе объекта
d    e┌───┐
───+─►│ P ├─┬───►
  -▲  └───┘ │
   │        │
   │  ┌───┐ │
   └──┤ C │◄┘
      └───┘
Код: feedback(1, C*P) или, что то же самое, input_sensitivity(P, C). В данном случае непосредственно между входом и выходом нет систем, поэтому мы вызываем feedback(1, C*P). Обратите внимание на порядок в выражении C*P: он важен для многомерных систем. Таким образом вычисляется функция чувствительности на входе объекта. Функция чувствительности чаще анализируется на выходе объекта, как показано ниже (для одномерных систем ситуация аналогичная).
Функция чувствительности на выходе объекта
      ┌───┐
───+─►│ P ├─+◄── e
  -▲  └───┘ │
   │        │y
   │  ┌───┐ │
   └──┤ C │◄┘
      └───┘
Код: feedback(1, P*C) или, что то же самое, output_sensitivity(P, C). Обратите внимание на то, что порядок в выражении    обратный в сравнении с приведенной выше функцией чувствительности на входе.
От опорного узла и возмущения на входе к выходу и управляющему сигналу . В этом примере формируется матрица передаточной функции с входами и и выходами и .
              d
     ┌─────┐  │  ┌─────┐
r    │     │u ▼  │     │ y
──+─►│  C  ├──+─►│  P  ├─┬─►
  ▲  │     │     │     │ │
 -│  └─────┘     └─────┘ │
  │                      │
  └──────────────────────┘
Код: feedback(C, P, W2=:, Z2=:, Zperm=[(1:P.ny).+P.nu; 1:P.nu]) # y,u from r,d. Для получения управляющего сигнала с правильным знаком в данном случае порядок следования P и C был изменен на обратный. Мы также использовали именованные аргументы W2 и Z2, чтобы указать, что входы и выходы P нужно включить как внешние входы и выходы, а также аргумент Zperm, чтобы указать порядок выходов ( 
Дробно-линейное преобразование
     ┌─────────┐
z◄───┤         │◄────w
     │    P    │
y┌───┤         │◄───┐u
 │   └─────────┘    │
 │                  │
 │      ┌───┐       │
 │      │   │       │
 └─────►│ K ├───────┘
        │   │
        └───┘
Код: lft(P, K)
      z1          z2
      ▲  ┌─────┐  ▲      ┌─────┐
      │  │     │  │      │     │
w1──+─┴─►│  C  ├──┴───+─►│  P  ├─┐
    │    │     │      │  │     │ │
    │    └─────┘      │  └─────┘ │
    │                 w2         │
    └────────────────────────────┘
Передаточная функция от  gangoffour).
Код: Для этой функции требуется пакет RobustAndOptimalControl.jl.
RobustAndOptimalControl.extended_gangoffour(P, C, pos=true)
# Для одномерного P
S  = G[1, 1]
PS = G[1, 2]
CS = G[2, 1]
T  = G[2, 2]
# Для многомерного 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]Дополнительные материалы
Это более сложная схема с рядом соединений, включая как прямые, так и соединения обратной связи. Ниже приводится ссылка на файл с кодом, в котором последовательно показано, как создавать такие сложные соединения с помощью именованных сигналов. В этом примере применяется пакет RobustAndOptimalControl.jl.
                 yF
              ┌────────────────────────────────┐
              │                                │
    ┌───────┐ │  ┌───────┐ yR   ┌─────────┐    │    ┌───────┐
uF  │       │ │  │       ├──────►         │ yC │  uP│       │    yP
────►   F   ├─┴──►   R   │      │    C    ├────+────►   P   ├────┬────►
    │       │    │       │   ┌──►         │         │       │    │
    └───────┘    └───────┘   │- └─────────┘         └───────┘    │
                             │                                   │
                             └───────────────────────────────────┘
См. пример кода complicated_feedback.jl.
Синтезирование фильтров
Фильтры можно синтезировать с помощью DSP.jl. В результате получаются объекты фильтров, типы которых определены в пакете DSP. Их можно преобразовывать в передаточные функции с помощью функции tf из ControlSystemsBase.
using DSP, ControlSystemsBase, Plots
fs = 100
df = digitalfilter(Bandpass(5, 10; fs), Butterworth(2))
G = tf(df, 1/fs) # Для получения правильной шакалы частот на графике Боде необходимо указать интервал дискретизации.
bodeplot(G, xscale=:identity, yscale=:identity, hz=true)Дополнительные материалы