Skip to content

Commit 2fc5879

Browse files
authored
Merge pull request #723 from JuliaControl/static
Static systems
2 parents 4699bfe + f5c6d86 commit 2fc5879

File tree

8 files changed

+412
-3
lines changed

8 files changed

+412
-3
lines changed

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
1717
MatrixEquations = "99c1a7ee-ab34-5fd5-8076-27c950a045f4"
1818
MatrixPencils = "48965c70-4690-11ea-1f13-43a2532b2fa8"
1919
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
20+
Polyester = "f517fe37-dbe3-4b94-8317-1923a5111588"
2021
Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
2122
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
2223
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
2324
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
2425
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
26+
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2527
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
2628

2729
[compat]
@@ -37,8 +39,10 @@ MacroTools = "0.5"
3739
MatrixEquations = "1, 2.1"
3840
MatrixPencils = "1.6"
3941
OrdinaryDiffEq = "5.2, 6.0"
42+
Polyester = "0.6"
4043
Polynomials = "1.1.10, 2.0, 3.0"
4144
RecipesBase = "1"
45+
StaticArrays = "1"
4246
julia = "1.6"
4347

4448
[extras]

docs/Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
88
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
99

1010
[compat]
11-
StaticArrays = "0.12"
1211
Documenter = "0.27.10"

src/ControlSystems.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export LTISystem,
1212
tf,
1313
zpk,
1414
isproper,
15+
StaticStateSpace,
16+
to_static,
17+
to_sized,
1518
# Linear Algebra
1619
balance,
1720
balance_statespace,
@@ -128,6 +131,7 @@ using DelayDiffEq
128131
using MacroTools
129132
using MatrixEquations
130133
using UUIDs # to load Plots in gangoffourplot
134+
using StaticArrays, Polyester
131135

132136
abstract type AbstractSystem end
133137

@@ -188,6 +192,8 @@ include("delay_systems.jl")
188192
include("hammerstein_weiner.jl")
189193
include("nonlinear_components.jl")
190194

195+
include("types/staticsystems.jl")
196+
191197
include("plotting.jl")
192198

193199
@deprecate pole poles

src/demo_systems.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function ssrand(T::Type, ny::Int, nu::Int, nstates::Int; proper=false, stable=tr
1212
A = randn(T, nstates, nstates)
1313
if stable
1414
Λ = eigvals(A)
15-
A = isnothing(Ts) ? A - 1.1*max(0, maximum(real(Λ)))*I : A*0.9/maximum(abs.(Λ))
15+
A = isnothing(Ts) ? A - T(1.1)*max(0, maximum(real(Λ)))*I : A*T(0.9)/maximum(abs.(Λ))
1616
end
1717

1818
B = randn(T, nstates, nu)

src/freqresp.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,10 @@ freqresp_nohess
223223
copyto!(Ri,D) # start with the D-matrix
224224
isinf(w_vec[i]) && continue
225225
w = _freq(w_vec[i], te)
226-
@views copyto!(Ac[Adiag],A[Adiag]) # reset storage to A
226+
# @views copyto!(Ac[Adiag],A[Adiag]) # reset storage to A
227+
for j in Adiag # Loop slightly faster
228+
Ac[j] = A[j] # reset storage to A
229+
end
227230
@views Ac[Adiag] .-= w # Ac = A - w*I
228231
Bc = Ac \ B # Bc = (A - w*I)\B # avoid inplace to handle sparse matrices etc.
229232
mul!(Ri, C, Bc, -1, 1) # use of 5-arg mul to subtract from D already in Ri. - rather than + since (A - w*I) instead of (w*I - A)

src/types/staticsystems.jl

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
const StaticStateSpace{TE, ny, nu, nx} = HeteroStateSpace{TE,
2+
<:SMatrix{nx,nx},
3+
<:SMatrix{nx,nu},
4+
<:SMatrix{ny,nx},
5+
<:SMatrix{ny,nu}
6+
}
7+
8+
9+
to_static(a::Number) = a
10+
to_static(a::AbstractArray) = SMatrix{size(a,1), size(a,2), eltype(a), prod(size(a))}(a)
11+
to_static(a::SArray) = a
12+
to_sized(a::Number) = a
13+
to_sized(a::AbstractArray) = SizedArray{Tuple{size(a)...}}(a)
14+
15+
function HeteroStateSpace(A,B,C,D,Ts=0,f::F=to_static) where F
16+
HeteroStateSpace(f(A),f(B),f(C),f(D),Ts)
17+
end
18+
19+
HeteroStateSpace(s,f) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.timeevol,f)
20+
21+
ControlSystems._string_mat_with_headers(a::SizedArray) = ControlSystems._string_mat_with_headers(Matrix(a))
22+
23+
24+
25+
"""
26+
StaticStateSpace(sys::AbstractStateSpace)
27+
28+
Return a [`HeteroStateSpace`](@ref) where the system matrices are of type SMatrix.
29+
30+
*NOTE: This function is fundamentally type unstable.* For maximum performance, create the static system manually, or make use of the function-barrier technique.
31+
"""
32+
StaticStateSpace(sys::AbstractStateSpace) = HeteroStateSpace(sys, to_static)
33+
34+
"""
35+
to_sized(sys::AbstractStateSpace)
36+
37+
Return a [`HeteroStateSpace`](@ref) where the system matrices are of type SizedMatrix.
38+
39+
*NOTE: This function is fundamentally type unstable.* For maximum performance, create the sized system manually, or make use of the function-barrier technique.
40+
"""
41+
to_sized(sys::AbstractStateSpace) = HeteroStateSpace(sys, to_sized)
42+
43+
StaticStateSpace(G::TransferFunction) = StaticStateSpace(ss(G))
44+
45+
# function to_static(sys::DelayLtiSystem)
46+
# innerP = to_static(sys.P.P)
47+
# partP = PartitionedStateSpace(innerP, sys.P.nu1, sys.P.ny1)
48+
# DelayLtiSystem(partP, sys.Tau)
49+
# end
50+
51+
52+
## Feedback
53+
# Special method to make sure we handle static systems in a type stable way
54+
55+
56+
function HeteroStateSpace(D::SArray, Ts=nothing)
57+
HeteroStateSpace(D, Ts === nothing ? Continuous() : Discrete(Ts))
58+
end
59+
60+
function HeteroStateSpace(D::SArray, timeevol::TimeEvolution)
61+
HeteroStateSpace(
62+
SMatrix{0,0,eltype(D),0}(),
63+
SMatrix{0,size(D,2),eltype(D),0}(),
64+
SMatrix{size(D,1),0,eltype(D),0}(),
65+
D,
66+
timeevol
67+
)
68+
end
69+
70+
function StaticStateSpace(D::Array, args...)
71+
HeteroStateSpace(to_static(D), args...)
72+
end
73+
74+
function Base.promote_rule(::Type{N}, ::Type{<:HeteroStateSpace{TE}}) where {N <: Number, TE}
75+
HeteroStateSpace{TE}
76+
end
77+
78+
function Base.convert(::Type{<:HeteroStateSpace{TE}}, n::Number) where TE
79+
if TE <: Continuous
80+
HeteroStateSpace(SMatrix{1,1,typeof(n),1}(n), Continuous())
81+
else
82+
HeteroStateSpace(SMatrix{1,1,typeof(n),1}(n), Discrete(UNDEF_SAMPLEPETIME))
83+
end
84+
end
85+
86+
function Base.promote_rule(::Type{StateSpace{TE1,T}}, ::Type{HeteroStateSpace{TE2,AT,BT,CT,DT}}) where {TE1,TE2,T,AT<:SMatrix,BT<:SMatrix,CT<:SMatrix,DT<:SMatrix}
87+
StateSpace{promote_type(TE1, TE2), promote_type(T, eltype(AT), eltype(BT), eltype(CT), eltype(DT))}
88+
end
89+
90+
91+
function Base.promote_rule(::Type{TransferFunction{TE1,T}}, ::Type{HeteroStateSpace{TE2,AT,BT,CT,DT}}) where {TE1,TE2,T,AT<:SMatrix,BT<:SMatrix,CT<:SMatrix,DT<:SMatrix}
92+
StateSpace{promote_type(TE1, TE2), promote_type(numeric_type(T), eltype(AT), eltype(BT), eltype(CT), eltype(DT))}
93+
end
94+
95+
96+
function feedback(sys1::StaticStateSpace, sys2::StaticStateSpace;
97+
U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=SVector{0,Int}(), Z2=SVector{0,Int}(),
98+
Wperm=:, Zperm=:, pos_feedback::Bool=false)
99+
100+
timeevol = common_timeevol(sys1,sys2)
101+
102+
if !(isa(Y1, Colon) || allunique(Y1)); @warn "Connecting single output to multiple inputs Y1=$Y1"; end
103+
if !(isa(Y2, Colon) || allunique(Y2)); @warn "Connecting single output to multiple inputs Y2=$Y2"; end
104+
if !(isa(U1, Colon) || allunique(U1)); @warn "Connecting multiple outputs to a single input U1=$U1"; end
105+
if !(isa(U2, Colon) || allunique(U2)); @warn "Connecting a single output to multiple inputs U2=$U2"; end
106+
107+
if (U1 isa Colon ? size(sys1, 2) : length(U1)) != (Y2 isa Colon ? size(sys2, 1) : length(Y2))
108+
error("Lengths of U1 ($U1) and Y2 ($Y2) must be equal")
109+
end
110+
if (U2 isa Colon ? size(sys2, 2) : length(U2)) != (Y1 isa Colon ? size(sys1, 1) : length(Y1))
111+
error("Lengths of U1 ($U2) and Y2 ($Y1) must be equal")
112+
end
113+
114+
α = pos_feedback ? 1 : -1 # The sign of feedback
115+
116+
s1_B1 = sys1.B[:,W1]
117+
s1_B2 = sys1.B[:,U1]
118+
s1_C1 = sys1.C[Z1,:]
119+
s1_C2 = sys1.C[Y1,:]
120+
s1_D11 = sys1.D[Z1,W1]
121+
s1_D12 = sys1.D[Z1,U1]
122+
s1_D21 = sys1.D[Y1,W1]
123+
s1_D22 = sys1.D[Y1,U1]
124+
125+
s2_B1 = sys2.B[:,W2]
126+
s2_B2 = sys2.B[:,U2]
127+
s2_C1 = sys2.C[Z2,:]
128+
s2_C2 = sys2.C[Y2,:]
129+
s2_D11 = sys2.D[Z2,W2]
130+
s2_D12 = sys2.D[Z2,U2]
131+
s2_D21 = sys2.D[Y2,W2]
132+
s2_D22 = sys2.D[Y2,U2]
133+
134+
if iszero(s1_D22) || iszero(s2_D22)
135+
A = [[sys1.A + α*s1_B2*s2_D22*s1_C2 α*s1_B2*s2_C2];
136+
[s2_B2*s1_C2 sys2.A + α*s2_B2*s1_D22*s2_C2]]
137+
138+
B = [[s1_B1 + α*s1_B2*s2_D22*s1_D21 α*s1_B2*s2_D21];
139+
[s2_B2*s1_D21 s2_B1 + α*s2_B2*s1_D22*s2_D21]]
140+
C = [[s1_C1 + α*s1_D12*s2_D22*s1_C2 α*s1_D12*s2_C2];
141+
[s2_D12*s1_C2 s2_C1 + α*s2_D12*s1_D22*s2_C2]]
142+
D = [[s1_D11 + α*s1_D12*s2_D22*s1_D21 α*s1_D12*s2_D21];
143+
[s2_D12*s1_D21 s2_D11 + α*s2_D12*s1_D22*s2_D21]]
144+
else
145+
R1 = try
146+
inv*I - s2_D22*s1_D22) # slightly faster than α*inv(I - α*s2_D22*s1_D22)
147+
catch
148+
error("Ill-posed feedback interconnection, I - α*s2_D22*s1_D22 or I - α*s2_D22*s1_D22 not invertible")
149+
end
150+
151+
R2 = try
152+
inv(I - α*s1_D22*s2_D22)
153+
catch
154+
error("Ill-posed feedback interconnection, I - α*s2_D22*s1_D22 or I - α*s2_D22*s1_D22 not invertible")
155+
end
156+
157+
A = [[sys1.A + s1_B2*R1*s2_D22*s1_C2 s1_B2*R1*s2_C2];
158+
[s2_B2*R2*s1_C2 sys2.A + α*s2_B2*R2*s1_D22*s2_C2]]
159+
160+
B = [[s1_B1 + s1_B2*R1*s2_D22*s1_D21 s1_B2*R1*s2_D21];
161+
[s2_B2*R2*s1_D21 s2_B1 + α*s2_B2*R2*s1_D22*s2_D21]]
162+
C = [[s1_C1 + s1_D12*R1*s2_D22*s1_C2 s1_D12*R1*s2_C2];
163+
[s2_D12*R2*s1_C2 s2_C1 + α*s2_D12*R2*s1_D22*s2_C2]]
164+
D = [[s1_D11 + s1_D12*R1*s2_D22*s1_D21 s1_D12*R1*s2_D21];
165+
[s2_D12*R2*s1_D21 s2_D11 + α*s2_D12*R2*s1_D22*s2_D21]]
166+
end
167+
168+
Dfinal = D[Zperm, Wperm]
169+
return HeteroStateSpace(A, B[:, Wperm], C[Zperm,:], Dfinal, timeevol)
170+
171+
end
172+
173+
function *(sys1::StaticStateSpace, sys2::StaticStateSpace)
174+
#Check dimension alignment
175+
#Note: sys1*sys2 = y <- sys1 <- sys2 <- u
176+
if (sys1.nu != sys2.ny) && (sys1.nu == 1 || sys2.ny == 1)
177+
throw(DimensionMismatch("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs. If you want to broadcast a scalar system to a diagonal system, use broadcasted multiplication sys1 .* sys2"))
178+
end
179+
sys1.nu == sys2.ny || throw(DimensionMismatch("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs"))
180+
timeevol = common_timeevol(sys1,sys2)
181+
T = promote_type(numeric_type(sys1), numeric_type(sys2))
182+
183+
O = @SMatrix zeros(size(sys2.A, 1), size(sys1.A, 2))
184+
A = [[sys1.A sys1.B*sys2.C];
185+
[O sys2.A]]
186+
B = [sys1.B*sys2.D ; sys2.B]
187+
C = [sys1.C sys1.D*sys2.C]
188+
D = sys1.D*sys2.D
189+
return HeteroStateSpace(A, B, C, D, timeevol)
190+
end
191+
192+
193+
function *(sys1::StaticStateSpace, sys2::AbstractStateSpace)
194+
sys1*StaticStateSpace(sys2)
195+
end
196+
197+
function *(sys1::AbstractStateSpace, sys2::StaticStateSpace)
198+
StaticStateSpace(sys1)*sys2
199+
end
200+
201+
202+
203+
@autovec () function freqresp_nohess!(R::Array{T,3}, sys::StaticStateSpace, w_vec::AbstractVector{W}) where {T, W <: Real}
204+
ny, nu = size(sys)
205+
@boundscheck size(R) == (ny,nu,length(w_vec))
206+
nx = sys.nx
207+
if nx == 0 # Only D-matrix
208+
@inbounds for i in eachindex(w_vec)
209+
R[:,:,i] .= sys.D
210+
end
211+
return R
212+
end
213+
A,B,C0,D = ssdata(sys)
214+
C = complex.(C0) # Still important when using ForwardDiff
215+
te = sys.timeevol
216+
217+
let R=R, A=A, B=B, C=C, D=D, te=te
218+
@inbounds Polyester.@batch for i in eachindex(w_vec)
219+
Ri = @views R[:,:,i]
220+
copyto!(Ri,D) # start with the D-matrix
221+
isinf(w_vec[i]) && continue
222+
w = _freq(w_vec[i], te)
223+
Ac = A - w*I
224+
Bc = Ac \ B # Bc = (A - w*I)\B
225+
Ri .-= C*Bc # - rather than + since (A - w*I) instead of (w*I - A)
226+
end
227+
end
228+
R
229+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ my_tests = [
1818
"test_result_types",
1919
"test_timeevol",
2020
"test_statespace",
21+
"test_staticsystems",
2122
"test_transferfunction",
2223
"test_zpk",
2324
"test_promotion",

0 commit comments

Comments
 (0)