Skip to content

Commit 3f243c0

Browse files
committed
Work on Padé approximations and general feedback interconnections
1 parent c7e2614 commit 3f243c0

File tree

7 files changed

+307
-94
lines changed

7 files changed

+307
-94
lines changed

src/ControlSystems.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export LTISystem,
1111
zpk,
1212
ss2tf,
1313
LQG,
14+
isproper,
1415
# Linear Algebra
1516
balance,
1617
care,
@@ -56,6 +57,8 @@ export LTISystem,
5657
parallel,
5758
feedback,
5859
feedback2dof,
60+
starprod,
61+
lft,
5962
# Discrete
6063
c2d,
6164
# Time Response
@@ -72,6 +75,7 @@ export LTISystem,
7275
sigma,
7376
# delay systems
7477
delay,
78+
pade,
7579
# utilities
7680
num, #Deprecated
7781
den, #Deprecated
@@ -89,6 +93,7 @@ using OrdinaryDiffEq, DelayDiffEq
8993
export Plots
9094
import Base: +, -, *, /, (==), (!=), isapprox, convert, promote_op
9195
import Base: getproperty
96+
import Base: exp # for exp(-sτ)
9297
import LinearAlgebra: BlasFloat
9398
export lyap # Make sure LinearAlgebra.lyap is available
9499
import Printf, Colors

src/connections.jl

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,202 @@ function blockdiag(mats::AbstractMatrix{T}...) where T
203203
end
204204
return res
205205
end
206+
207+
208+
209+
"""
210+
`feedback(L)` Returns L/(1+L)
211+
`feedback(P1,P2)` Returns P1/(1+P1*P2)
212+
"""
213+
feedback(L::TransferFunction) = L/(1+L)
214+
feedback(P1::TransferFunction, P2::TransferFunction) = P1/(1+P1*P2)
215+
216+
#Efficient implementations
217+
function feedback(L::TransferFunction{T}) where T<:SisoRational
218+
if size(L) != (1,1)
219+
error("MIMO TransferFunction feedback isn't implemented, use L/(1+L)")
220+
end
221+
P = numpoly(L)
222+
Q = denpoly(L)
223+
tf(P, P+Q, L.Ts)
224+
end
225+
226+
function feedback(L::TransferFunction{T}) where {T<:SisoZpk}
227+
if size(L) != (1,1)
228+
error("MIMO TransferFunction feedback isn't implemented, use L/(1+L)")
229+
end
230+
#Extract polynomials and create P/(P+Q)
231+
k = L.matrix[1].k
232+
denpol = numpoly(L)[1]+denpoly(L)[1]
233+
kden = denpol[end] # Get coeff of s^n
234+
# Create siso system
235+
sisozpk = T(L.matrix[1].z, roots(denpol), k/kden)
236+
return TransferFunction{T}(fill(sisozpk,1,1), L.Ts)
237+
end
238+
239+
"""
240+
`feedback(sys)`
241+
242+
`feedback(sys1,sys2)`
243+
244+
Forms the negative feedback interconnection
245+
```julia
246+
>-+ sys1 +-->
247+
| |
248+
(-)sys2 +
249+
```
250+
If no second system is given, negative identity feedback is assumed
251+
"""
252+
function feedback(sys::Union{StateSpace, DelayLtiSystem})
253+
ninputs(sys) != noutputs(sys) && error("Use feedback(sys1, sys2) if number of inputs != outputs")
254+
feedback(sys,ss(Matrix{numeric_type(sys)}(I,size(sys)...)))
255+
end
256+
257+
function feedback(sys1::StateSpace,sys2::StateSpace)
258+
sum(abs.(sys1.D)) != 0 && sum(abs.(sys2.D)) != 0 && error("There can not be a direct term (D) in both sys1 and sys2")
259+
A = [sys1.A+sys1.B*(-sys2.D)*sys1.C sys1.B*(-sys2.C); sys2.B*sys1.C sys2.A+sys2.B*sys1.D*(-sys2.C)]
260+
B = [sys1.B; sys2.B*sys1.D]
261+
C = [sys1.C sys1.D*(-sys2.C)]
262+
ss(A,B,C,sys1.D)
263+
end
264+
265+
266+
"""
267+
feedback(s1::AbstractStateSpace, s2::AbstractStateSpace;
268+
U1=1:size(s1,2), Y1=1:size(s1,1), U2=1:size(s2,2), Y2=1:size(s2,1),
269+
W1=1:size(s1,2), Z1=1:size(s1,1), W2=Int[], Z2=Int[],
270+
w_reorder=nothing, z_reorder=nothing,
271+
neg_feeback::Bool=true)
272+
273+
274+
`U1`, `Y1`, `U2`, `Y2` contains the indices of the signals that should be connected.
275+
`W1`, `Z1`, `W2`, `Z2` contains the signal indices of `s1` and `s2` that should be kept.
276+
277+
Specify `Wreorder` and `Zreorder` to reorder [W1; W2] and [Z1; Z2] after matrix multiplications.
278+
279+
Negative feedback is the default. Specify `neg_feedback=false` for positive feedback.
280+
281+
See Zhou etc. for similar (somewhat less symmetric) formulas.
282+
"""
283+
function feedback(s1::AbstractStateSpace, s2::AbstractStateSpace;
284+
U1=1:size(s1,2), Y1=1:size(s1,1), U2=1:size(s2,2), Y2=1:size(s2,1),
285+
W1=1:size(s1,2), Z1=1:size(s1,1), W2=Int[], Z2=Int[],
286+
w_reorder=nothing, z_reorder=nothing,
287+
neg_feedback::Bool=true)
288+
289+
if !(allunique(Y1) && allunique(Y2))
290+
warning("Connecting a single output to multiple inputs.")
291+
end
292+
if !(allunique(U1) && allunique(U2))
293+
warning("Connecting multiple outputs to a single input.")
294+
end
295+
296+
α = neg_feedback ? -1 : 1 # The sign of feedback
297+
298+
s1_B1 = s1.B[:,W1]
299+
s1_B2 = s1.B[:,U1]
300+
s1_C1 = s1.C[Z1,:]
301+
s1_C2 = s1.C[Y1,:]
302+
s1_D11 = s1.D[Z1,W1]
303+
s1_D12 = s1.D[Z1,U1]
304+
s1_D21 = s1.D[Y1,W1]
305+
s1_D22 = s1.D[Y1,U1]
306+
307+
s2_B1 = s2.B[:,W2]
308+
s2_B2 = s2.B[:,U2]
309+
s2_C1 = s2.C[Z2,:]
310+
s2_C2 = s2.C[Y2,:]
311+
s2_D11 = s2.D[Z2,W2]
312+
s2_D12 = s2.D[Z2,U2]
313+
s2_D21 = s2.D[Y2,W2]
314+
s2_D22 = s2.D[Y2,U2]
315+
316+
317+
if iszero(s1_D22) || iszero(s2_D22)
318+
A = [s1.A + α*s1_B2*s2_D22*s1_C2 α*s1_B2*s2_C2;
319+
s2_B2*s1_C2 s2.A + α*s2_B2*s1_D22*s2_C2]
320+
321+
B = [s1_B1 + α*s1_B2*s2_D22*s1_D21 α*s1_B2*s2_D21;
322+
s2_B2*s1_D21 s2_B1 + α*s2_B2*s1_D22*s2_D21]
323+
C = [s1_C1 + α*s1_D12*s2_D22*s1_C2 α*s1_D12*s2_C2;
324+
s2_D12*s1_C2 s2_C1 + α*s2_D12*s1_D22*s2_C2]
325+
D = [s1_D11 + α*s1_D12*s2_D22*s1_D21 α*s1_D12*s2_D21;
326+
s2_D12*s1_D21 s2_D11 + α*s2_D12*s1_D22*s2_D21]
327+
else
328+
R1 = inv(I - α*s2_D22*s1_D22) # inv seems to be better than lu
329+
R2 = inv(I - α*s1_D22*s2_D22)
330+
331+
A = [s1.A + α*s1_B2*R1*s2_D22*s1_C2 α*s1_B2*R1*s2_C2;
332+
s2_B2*R2*s1_C2 s2.A + α*s2_B2*R2*s1_D22*s2_C2]
333+
334+
B = [s1_B1 + α*s1_B2*R1*s2_D22*s1_D21 α*s1_B2*R1*s2_D21;
335+
s2_B2*R2*s1_D21 s2_B1 + α*s2_B2*R2*s1_D22*s2_D21]
336+
C = [s1_C1 + α*s1_D12*R1*s2_D22*s1_C2 α*s1_D12*R1*s2_C2;
337+
s2_D12*R2*s1_C2 s2_C1 + α*s2_D12*R2*s1_D22*s2_C2]
338+
D = [s1_D11 + α*s1_D12*R1*s2_D22*s1_D21 α*s1_D12*R1*s2_D21;
339+
s2_D12*R2*s1_D21 s2_D11 + α*s2_D12*R2*s1_D22*s2_D21]
340+
end
341+
# Return while reorganizing into the desired order
342+
return StateSpace(A, isnothing(w_reorder) ? B : B[:, w_reorder],
343+
isnothing(z_reorder) ? C : C[z_reorder,:],
344+
isnothing(w_reorder) && isnothing(z_reorder) ? D : D[z_reorder, w_reorder])
345+
#return StateSpace(A, B_tmp, C_tmp, D_tmp)
346+
end
347+
348+
"""
349+
`feedback2dof(P,R,S,T)` Return `BT/(AR+ST)` where B and A are the numerator and denomenator polynomials of `P` respectively
350+
`feedback2dof(B,A,R,S,T)` Return `BT/(AR+ST)`
351+
"""
352+
function feedback2dof(P::TransferFunction,R,S,T)
353+
!issiso(P) && error("Feedback not implemented for MIMO systems")
354+
tf(conv(poly2vec(numpoly(P)[1]),T),zpconv(poly2vec(denpoly(P)[1]),R,poly2vec(numpoly(P)[1]),S))
355+
end
356+
357+
feedback2dof(B,A,R,S,T) = tf(conv(B,T),zpconv(A,R,B,S))
358+
359+
360+
"""
361+
lft(G, Δ, type=:l)
362+
363+
Upper linear fractional transformation between systems `G` and `Δ`.
364+
`G` must have more inputs and outputs than `Δ` has outputs and inputs.
365+
366+
For details, see Chapter 9.1 in
367+
**Zhou, K. and JC Doyle**. Essentials of robust control, Prentice hall (NJ), 1998
368+
"""
369+
function lft(G, Δ, type=:l) # QUESTION: Or lft_u, and lft_l instead?
370+
371+
if !(G.nu > Δ.ny && G.ny > Δ.nu)
372+
error("Must have G.nu > Δ.ny and G.ny > Δ.nu for lower/upper lft.")
373+
end
374+
375+
if type == :l
376+
feedback(G, Δ, U1=G.nu-Δ.ny+1:G.nu, Y1=G.ny-Δ.nu+1:G.ny, W1=1:G.ny-Δ.nu, Z1=1:G.nu-Δ.ny, neg_feedback=false)
377+
elseif type == :u
378+
feedback(G, Δ, U1=1:Δ.ny, Y1=1:Δ.nu, W1=Δ.nu+1:G.ny, Z1=Δ.nu+1:G.ny, neg_feedback=false)
379+
else
380+
error("Invalid type of lft ($type). Specify type=:l (:u) for lower (upper) lft.")
381+
end
382+
end
383+
384+
385+
"""
386+
starprod(sys1, sys2, dimu, dimy)
387+
388+
Compute the Redheffer star product.
389+
390+
`length(U1) = length(Y2) = dimu` and `length(Y1) = length(U2) = dimy`
391+
392+
For details, see Chapter 9.3 in
393+
**Zhou, K. and JC Doyle**. Essentials of robust control, Prentice hall (NJ), 1998
394+
"""
395+
function starprod(G1, G2, dimy::Int, dimu::Int)
396+
feedback(G1, G2, U1=G1.nu-dimu+1:G1.nu, Y1=G1.ny-dimy+1:G1.ny, W1=1:G1.nu-dimu, Z1=1:G1.ny-dimy,
397+
U2=1:dimy, Y2=1:dimu, W2=dimy+1:G2.nu, Z2=dimu+1:G2.ny,
398+
neg_feedback=false)
399+
end
400+
function starprod(sys1, sys2)
401+
nu = size(sys2, 2)
402+
ny = size(sys2, 1)
403+
starp(sys1, sys2, nu, ny)
404+
end

src/delay_systems.jl

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,62 @@ function impulse(sys::DelayLtiSystem{T}, t::AbstractVector; alg=MethodOfSteps(BS
201201
end
202202
return y, tout, x
203203
end
204+
205+
206+
# Used for pade approximation
207+
"""
208+
`p2 = _linscale(p::Poly, a)`
209+
210+
Given a polynomial `p` and a number `a, returns the polynomials `p2` such that
211+
`p2(s) == p(a*s)`.
212+
"""
213+
function _linscale(p::Poly, a)
214+
# This function should perhaps be implemented in Polynomials.jl
215+
coeffs_scaled = similar(p.a, promote_type(eltype(p), typeof(a)))
216+
a_pow = 1
217+
coeffs_scaled[1] = p.a[1]
218+
for k=2:length(p.a)
219+
a_pow *= a
220+
coeffs_scaled[k] = p.a[k]*a_pow
221+
end
222+
return Poly(coeffs_scaled)
223+
end
224+
225+
# Coefficeints for Padé approximations
226+
# Q_COEFFS = [Poly([binomial(N,i)*prod(N+1:2*N-i) for i=0:N]) for N=1:10]
227+
const PADE_Q_COEFFS = [[2, 1],
228+
[12, 6, 1],
229+
[120, 60, 12, 1],
230+
[1680, 840, 180, 20, 1],
231+
[30240, 15120, 3360, 420, 30, 1],
232+
[665280, 332640, 75600, 10080, 840, 42, 1],
233+
[17297280, 8648640, 1995840, 277200, 25200, 1512, 56, 1],
234+
[518918400, 259459200, 60540480, 8648640, 831600, 55440, 2520, 72, 1],
235+
[17643225600, 8821612800, 2075673600, 302702400, 30270240, 2162160, 110880, 3960, 90, 1],
236+
[670442572800, 335221286400, 79394515200, 11762150400, 1210809600, 90810720, 5045040, 205920, 5940, 110, 1]]
237+
238+
"""
239+
pade(τ::Real, N::Int)
240+
241+
Compute the `N`th order Padé approximation of a time-delay of length `τ`.
242+
"""
243+
function pade::Real, N::Int)
244+
if !(1 <= N <= 10); error("Order of Padé approximation must be between 1 and 10. Got $N."); end
245+
246+
Q = Poly(PADE_Q_COEFFS[N])
247+
248+
return tf(_linscale(Q, -τ), _linscale(Q, τ)) # return Q(-τs)/Q(τs)
249+
end
250+
251+
252+
"""
253+
pade(G::DelayLtiSystem, N)
254+
255+
Approximate all time-delays in `G` by Padé approximations of degree `N`.
256+
"""
257+
function pade(G::DelayLtiSystem, N)
258+
ny, nu = size(G)
259+
nTau = length(G.Tau)
260+
X = append([ss(pade(τ,N)) for τ in G.Tau]...) # Perhaps append should be renamed blockdiag
261+
return lft(G.P.P, X)
262+
end

src/synthesis.jl

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -143,72 +143,3 @@ function acker(A,B,P)
143143
end
144144
return [zeros(1,n-1) 1]*(S\q)
145145
end
146-
147-
148-
"""
149-
`feedback(L)` Returns L/(1+L)
150-
`feedback(P1,P2)` Returns P1/(1+P1*P2)
151-
"""
152-
feedback(L::TransferFunction) = L/(1+L)
153-
feedback(P1::TransferFunction, P2::TransferFunction) = P1/(1+P1*P2)
154-
155-
#Efficient implementations
156-
function feedback(L::TransferFunction{T}) where T<:SisoRational
157-
if size(L) != (1,1)
158-
error("MIMO TransferFunction feedback isn't implemented, use L/(1+L)")
159-
end
160-
P = numpoly(L)
161-
Q = denpoly(L)
162-
tf(P, P+Q, L.Ts)
163-
end
164-
165-
function feedback(L::TransferFunction{T}) where {T<:SisoZpk}
166-
if size(L) != (1,1)
167-
error("MIMO TransferFunction feedback isn't implemented, use L/(1+L)")
168-
end
169-
#Extract polynomials and create P/(P+Q)
170-
k = L.matrix[1].k
171-
denpol = numpoly(L)[1]+denpoly(L)[1]
172-
kden = denpol[end] # Get coeff of s^n
173-
# Create siso system
174-
sisozpk = T(L.matrix[1].z, roots(denpol), k/kden)
175-
return TransferFunction{T}(fill(sisozpk,1,1), L.Ts)
176-
end
177-
178-
"""
179-
`feedback(sys)`
180-
181-
`feedback(sys1,sys2)`
182-
183-
Forms the negative feedback interconnection
184-
```julia
185-
>-+ sys1 +-->
186-
| |
187-
(-)sys2 +
188-
```
189-
If no second system is given, negative identity feedback is assumed
190-
"""
191-
function feedback(sys::Union{StateSpace, DelayLtiSystem})
192-
ninputs(sys) != noutputs(sys) && error("Use feedback(sys1, sys2) if number of inputs != outputs")
193-
feedback(sys,ss(Matrix{numeric_type(sys)}(I,size(sys)...)))
194-
end
195-
196-
function feedback(sys1::StateSpace,sys2::StateSpace)
197-
sum(abs.(sys1.D)) != 0 && sum(abs.(sys2.D)) != 0 && error("There can not be a direct term (D) in both sys1 and sys2")
198-
A = [sys1.A+sys1.B*(-sys2.D)*sys1.C sys1.B*(-sys2.C); sys2.B*sys1.C sys2.A+sys2.B*sys1.D*(-sys2.C)]
199-
B = [sys1.B; sys2.B*sys1.D]
200-
C = [sys1.C sys1.D*(-sys2.C)]
201-
ss(A,B,C,sys1.D)
202-
end
203-
204-
205-
"""
206-
`feedback2dof(P,R,S,T)` Return `BT/(AR+ST)` where B and A are the numerator and denomenator polynomials of `P` respectively
207-
`feedback2dof(B,A,R,S,T)` Return `BT/(AR+ST)`
208-
"""
209-
function feedback2dof(P::TransferFunction,R,S,T)
210-
!issiso(P) && error("Feedback not implemented for MIMO systems")
211-
tf(conv(poly2vec(numpoly(P)[1]),T),zpconv(poly2vec(denpoly(P)[1]),R,poly2vec(numpoly(P)[1]),S))
212-
end
213-
214-
feedback2dof(B,A,R,S,T) = tf(conv(B,T),zpconv(A,R,B,S))

0 commit comments

Comments
 (0)