Skip to content

Commit c1cf26a

Browse files
authored
Merge pull request #971 from JuliaControl/tf2ss
improve tf2ss conversion
2 parents c66b3d1 + aba649d commit c1cf26a

File tree

9 files changed

+90
-87
lines changed

9 files changed

+90
-87
lines changed

docs/src/man/creating_systems.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ in discrete time, is created using
102102
```julia
103103
ss(A,B,C,D) # Continuous-time system
104104
ss(A,B,C,D,Ts) # Discrete-time system
105+
ss(P; balance=true, minimal=false) # Convert transfer function P to state space
105106
```
106107
and they behave similarly to transfer functions.
107108

@@ -118,7 +119,7 @@ HeteroStateSpace(sys, to_static)
118119
```
119120
Notice the different matrix types used.
120121

121-
To associate **names** with states, inputs and outputs, see [`named_ss`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/#Named-systems) from RobustAndOptimalControl.jl.
122+
To associate **names** with state variables, inputs and outputs, see [`named_ss`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/#Named-systems) from RobustAndOptimalControl.jl.
122123

123124

124125
## Converting between types
@@ -137,6 +138,8 @@ Sample Time: 0.1 (seconds)
137138
Discrete-time transfer function model
138139
```
139140

141+
When a transfer function `P` is converted to a state-space model using `ss(P; balance=true, minimal=false)`, the user may choose whether to balance the state-space model (default=true) and/or to return a minimal realization (default=false).
142+
140143

141144
## Converting between continuous and discrete time
142145
A continuous-time system represents differential equations or a transfer function in the [Laplace domain](https://en.wikipedia.org/wiki/Laplace_transform), while a discrete-time system represents difference equations or a transfer function in the [Z-domain](https://en.wikipedia.org/wiki/Z-transform).

lib/ControlSystemsBase/src/timeresp.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ end
7171

7272
Base.step(sys::LTISystem, tfinal::Real; kwargs...) = step(sys, _default_time_vector(sys, tfinal); kwargs...)
7373
Base.step(sys::LTISystem; kwargs...) = step(sys, _default_time_vector(sys); kwargs...)
74-
Base.step(sys::TransferFunction, t::AbstractVector; kwargs...) = step(ss(sys), t::AbstractVector; kwargs...)
74+
Base.step(sys::TransferFunction, t::AbstractVector; kwargs...) = step(ss(sys, minimal=numeric_type(sys) isa BlasFloat), t::AbstractVector; kwargs...)
7575

7676
"""
7777
y, t, x = impulse(sys[, tfinal])
@@ -119,7 +119,7 @@ end
119119

120120
impulse(sys::LTISystem, tfinal::Real; kwargs...) = impulse(sys, _default_time_vector(sys, tfinal); kwargs...)
121121
impulse(sys::LTISystem; kwargs...) = impulse(sys, _default_time_vector(sys); kwargs...)
122-
impulse(sys::TransferFunction, t::AbstractVector; kwargs...) = impulse(ss(sys), t; kwargs...)
122+
impulse(sys::TransferFunction, t::AbstractVector; kwargs...) = impulse(ss(sys, minimal=numeric_type(sys) isa BlasFloat), t; kwargs...)
123123

124124
"""
125125
result = lsim(sys, u[, t]; x0, method])
@@ -301,7 +301,7 @@ function lsim(sys::AbstractStateSpace, u::Function, t::AbstractVector;
301301
end
302302

303303

304-
lsim(sys::TransferFunction, args...; kwargs...) = lsim(ss(sys), args...; kwargs...)
304+
lsim(sys::TransferFunction, args...; kwargs...) = lsim(ss(sys, minimal=numeric_type(sys) isa BlasFloat), args...; kwargs...)
305305

306306

307307
"""

lib/ControlSystemsBase/src/types/StateSpace.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ StateSpace(sys::LTISystem; kwargs...) = convert(StateSpace, sys; kwargs...)
128128
"""
129129
sys = ss(A, B, C, D) # Continuous
130130
sys = ss(A, B, C, D, Ts) # Discrete
131+
sys = ss(P::TransferFunction; balance=true, minimal=false) # Convert TransferFunction to StateSpace
131132
132133
Create a state-space model `sys::StateSpace{TE, T}`
133134
with matrix element type `T` and TE is `Continuous` or `<:Discrete`.
@@ -138,7 +139,9 @@ Otherwise, this is a discrete-time model with sampling period `Ts`.
138139
`D` may be specified as `0` in which case a zero matrix of appropriate size is constructed automatically.
139140
`sys = ss(D [, Ts])` specifies a static gain matrix `D`.
140141
141-
To associate names with states, inputs and outputs, see [`named_ss`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/#Named-systems).
142+
When a transfer function `P` is converted to a state-space model, the user may choose whether to balance the state-space model (default=true) and/or to return a minimal realization (default=false). This conversion has more keyword argument options, see `?ControlSystemsBase.MatrixPencils.rm2ls` for additional details.
143+
144+
To associate names with state variables, inputs and outputs, see [`named_ss`](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/#Named-systems).
142145
"""
143146
ss(args...;kwargs...) = StateSpace(args...;kwargs...)
144147

lib/ControlSystemsBase/src/types/conversion.jl

Lines changed: 58 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,3 @@
1-
# Base.convert(::Type{<:SisoTf}, b::Real) = Base.convert(SisoRational, b)
2-
# Base.convert{T<:Real}(::Type{<:SisoZpk}, b::T) = SisoZpk(T[], T[], b)
3-
# Base.convert{T<:Real}(::Type{<:SisoRational}, b::T) = SisoRational([b], [one(T)])
4-
# Base.convert{T1}(::Type{SisoRational{Vector{T1}}}, t::SisoRational) = SisoRational(Polynomial(T1.(t.num.coeffs$1),Polynomial(T1.(t.den.coeffs$1))
5-
# Base.convert(::Type{<:StateSpace}, t::Real) = ss(t)
6-
#
7-
8-
#
9-
# function Base.convert{T<:AbstractMatrix{<:Number}}(::Type{StateSpace{T}}, s::StateSpace)
10-
# AT = promote_type(T, arraytype(s))
11-
# StateSpace{AT}(AT(s.A),AT(s.B),AT(s.C),AT(s.D), s.timeevol, s.statenames, s.inputnames, s.outputnames)
12-
# end
13-
141
# TODO Fix these to use proper constructors
152
# NOTE: no real need to convert numbers to transfer functions, have addition methods..
163
# How to convert a number to either Continuous or Discrete transfer function
@@ -42,19 +29,7 @@ Base.convert(::Type{TransferFunction{TE,SisoZpk{T,TR}}}, d::Number) where {TE, T
4229

4330
Base.convert(::Type{StateSpace{TE,T}}, d::Number) where {TE, T} =
4431
convert(StateSpace{TE,T}, fill(d, (1,1)))
45-
#
46-
# Base.convert(::Type{TransferFunction{Continuous,<:SisoRational{T}}}, b::Number) where {T} = tf(T(b), Continuous())
47-
# Base.convert(::Type{TransferFunction{Continuous,<:SisoZpk{T, TR}}}, b::Number) where {T, TR} = zpk(T(b), Continuous())
48-
# Base.convert(::Type{StateSpace{Continuous,T, MT}}, b::Number) where {T, MT} = convert(StateSpace{Continuous,T, MT}, fill(b, (1,1)))
49-
50-
# function Base.convert{T<:Real,S<:TransferFunction}(::Type{S}, b::VecOrMat{T})
51-
# r = Matrix{S}(size(b,2),1)
52-
# for j=1:size(b,2)
53-
# r[j] = vcat(map(k->convert(S,k),b[:,j])...)
54-
# end
55-
# hcat(r...)
56-
# end
57-
#
32+
5833

5934
function convert(::Type{TransferFunction{TE,S}}, G::TransferFunction) where {TE,S}
6035
Gnew_matrix = convert.(S, G.matrix)
@@ -92,77 +67,85 @@ struct ImproperException <: Exception end
9267
Base.showerror(io::IO, e::ImproperException) = print(io, "System is improper, a state-space representation is impossible")
9368

9469
# Note: balancing is only applied by default for floating point types, integer systems are not balanced since that would change the type.
95-
function Base.convert(::Type{StateSpace{TE,T}}, G::TransferFunction; balance=!(T <: Union{Integer, Rational, ForwardDiff.Dual})) where {TE,T<:Number}
96-
if !isproper(G)
97-
throw(ImproperException())
98-
end
70+
function Base.convert(::Type{StateSpace{TE,T}}, G::TransferFunction; balance=!(T <: Union{Integer, Rational, ForwardDiff.Dual}), minimal=false, contr=minimal, obs=minimal, noseig=minimal, kwargs...) where {TE,T<:Number}
9971

100-
ny, nu = size(G)
10172

102-
# A, B, C, D matrices for each element of the transfer function matrix
103-
abcd_vec = [siso_tf_to_ss(T, g) for g in G.matrix[:]]
73+
NUM = numpoly(G)
74+
DEN = denpoly(G)
75+
(A, E, B, C, D, blkdims) = MatrixPencils.rm2ls(NUM, DEN; contr, obs, noseig, minimal, kwargs...)
76+
E == I || throw(ImproperException())
10477

105-
# Number of states for each transfer function element realization
106-
nvec = [size(abcd[1], 1) for abcd in abcd_vec]
107-
ntot = sum(nvec)
10878

109-
A = zeros(T, (ntot, ntot))
110-
B = zeros(T, (ntot, nu))
111-
C = zeros(T, (ny, ntot))
112-
D = zeros(T, (ny, nu))
79+
# if !isproper(G)
80+
# throw(ImproperException())
81+
# end
11382

114-
inds = -1:0
115-
for j=1:nu
116-
for i=1:ny
117-
k = (j-1)*ny + i
83+
# ny, nu = size(G)
11884

119-
# states corresponding to the transfer function element (i,j)
120-
inds = (inds.stop+1):(inds.stop+nvec[k])
85+
# # A, B, C, D matrices for each element of the transfer function matrix
86+
# abcd_vec = [siso_tf_to_ss(T, g) for g in G.matrix[:]]
12187

122-
A[inds,inds], B[inds,j:j], C[i:i,inds], D[i:i,j:j] = abcd_vec[k]
123-
end
124-
end
88+
# # Number of states for each transfer function element realization
89+
# nvec = [size(abcd[1], 1) for abcd in abcd_vec]
90+
# ntot = sum(nvec)
91+
92+
# A = zeros(T, (ntot, ntot))
93+
# B = zeros(T, (ntot, nu))
94+
# C = zeros(T, (ny, ntot))
95+
# D = zeros(T, (ny, nu))
96+
97+
# inds = -1:0
98+
# for j=1:nu
99+
# for i=1:ny
100+
# k = (j-1)*ny + i
101+
102+
# # states corresponding to the transfer function element (i,j)
103+
# inds = (inds.stop+1):(inds.stop+nvec[k])
104+
105+
# A[inds,inds], B[inds,j:j], C[i:i,inds], D[i:i,j:j] = abcd_vec[k]
106+
# end
107+
# end
125108
if balance
126109
A, B, C = balance_statespace(A, B, C)
127110
end
128111
return StateSpace{TE,T}(A, B, C, D, TE(G.timeevol))
129112
end
130113

131-
siso_tf_to_ss(T::Type, f::SisoTf) = siso_tf_to_ss(T, convert(SisoRational, f))
114+
# siso_tf_to_ss(T::Type, f::SisoTf) = siso_tf_to_ss(T, convert(SisoRational, f))
132115

133116
# Conversion to statespace on controllable canonical form
134-
function siso_tf_to_ss(T::Type, f::SisoRational)
117+
# function siso_tf_to_ss(T::Type, f::SisoRational)
135118

136-
num0, den0 = numvec(f), denvec(f)
137-
# Normalize the numerator and denominator to allow realization of transfer functions
138-
# that are proper, but not strictly proper
139-
num = num0 ./ den0[1]
140-
den = den0 ./ den0[1]
119+
# num0, den0 = numvec(f), denvec(f)
120+
# # Normalize the numerator and denominator to allow realization of transfer functions
121+
# # that are proper, but not strictly proper
122+
# num = num0 ./ den0[1]
123+
# den = den0 ./ den0[1]
141124

142-
N = length(den) - 1 # The order of the rational function f
125+
# N = length(den) - 1 # The order of the rational function f
143126

144-
# Get numerator coefficient of the same order as the denominator
145-
bN = length(num) == N+1 ? num[1] : zero(eltype(num))
127+
# # Get numerator coefficient of the same order as the denominator
128+
# bN = length(num) == N+1 ? num[1] : zero(eltype(num))
146129

147-
@views if N == 0 #|| num == zero(Polynomial{T})
148-
A = zeros(T, 0, 0)
149-
B = zeros(T, 0, 1)
150-
C = zeros(T, 1, 0)
151-
else
152-
A = diagm(1 => ones(T, N-1))
153-
A[end, :] .= .-reverse(den)[1:end-1]
130+
# @views if N == 0 #|| num == zero(Polynomial{T})
131+
# A = zeros(T, 0, 0)
132+
# B = zeros(T, 0, 1)
133+
# C = zeros(T, 1, 0)
134+
# else
135+
# A = diagm(1 => ones(T, N-1))
136+
# A[end, :] .= .-reverse(den)[1:end-1]
154137

155-
B = zeros(T, N, 1)
156-
B[end] = one(T)
138+
# B = zeros(T, N, 1)
139+
# B[end] = one(T)
157140

158-
C = zeros(T, 1, N)
159-
C[1:min(N, length(num))] = reverse(num)[1:min(N, length(num))]
160-
C[:] .-= bN .* reverse(den)[1:end-1] # Can index into polynomials at greater inddices than their length
161-
end
162-
D = fill(bN, 1, 1)
141+
# C = zeros(T, 1, N)
142+
# C[1:min(N, length(num))] = reverse(num)[1:min(N, length(num))]
143+
# C[:] .-= bN .* reverse(den)[1:end-1] # Can index into polynomials at greater inddices than their length
144+
# end
145+
# D = fill(bN, 1, 1)
163146

164-
return A, B, C, D
165-
end
147+
# return A, B, C, D
148+
# end
166149

167150
"""
168151
A, B, C, T = balance_statespace{S}(A::Matrix{S}, B::Matrix{S}, C::Matrix{S}, perm::Bool=false)

lib/ControlSystemsBase/test/test_complex.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ s = tf("s");
3737
@test tzeros(ss([-1.0-im 1-im; 2 0], [2; 0], [-1+1im -0.5-1.25im], 1)) [-1-2im, 2-im]
3838

3939
@test tzeros(ss((s-2.0-1.5im)^3/(s+1+im)/(s+2)^3)) fill(2.0 + 1.5im, 3) rtol=1e-4
40-
@test tzeros(ss((s-2.0-1.5im)*(s-3.0)/(s+1+im)/(s+2)^2)) [3.0, 2.0 + 1.5im] rtol=1e-14
40+
@test tzeros(ss((s-2.0-1.5im)*(s-3.0)/(s+1+im)/(s+2)^2)) [2.0 + 1.5im, 3.0] rtol=1e-14
4141

4242
end

lib/ControlSystemsBase/test/test_conversion.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ A = randn(ComplexF64,3,3)
88

99
G = tf(1.0,[1,1])
1010
H = zpk([0.0], [1.0], 1.0)
11-
@inferred ControlSystemsBase.siso_tf_to_ss(Float64, G.matrix[1,1])
12-
@inferred ControlSystemsBase.siso_tf_to_ss(Float64, H.matrix[1,1])
11+
# @inferred ControlSystemsBase.siso_tf_to_ss(Float64, G.matrix[1,1])
12+
# @inferred ControlSystemsBase.siso_tf_to_ss(Float64, H.matrix[1,1])
1313

1414
# Easy second order system
1515
sys1 = ss([-1 0;1 1],[1;0],[1 1],0)

lib/ControlSystemsBase/test/test_implicit_diff.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,17 @@ J2 = fdgrad(difffun, pars)[:]
212212
# @show norm(J1-J2)
213213
@test J1 J2 rtol = 1e-5
214214

215+
## Test differentiation of tf 2 ss conversion
216+
217+
function difffun(pars)
218+
P = tf(1, pars)
219+
sum(abs2, step(P, 0:0.1:10, method=:tustin).y +
220+
impulse(P, 0:0.1:10, method=:tustin).y
221+
)
222+
end
223+
224+
pars = [1.0, 2, 1]
225+
226+
g1 = ForwardDiff.gradient(difffun, pars)
227+
g2 = fdgrad(difffun, pars)
228+
@test g1 g2 rtol = 1e-5

lib/ControlSystemsBase/test/test_timeresp.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ xreal = zeros(3, length(t0), 2)
147147
y, t, x = step(systf, t0, method=:zoh) # Method is :zoh so discretization is applied
148148
yreal[1,:,1] = 1 .- exp.(-t)
149149
yreal[2,:,2] = -1 .+ exp.(-t) + 2*exp.(-t).*t
150-
@test y yreal atol=1e-14
150+
@test y yreal atol=1e-13
151151
#Step ss
152152
y, t, x = step(sysss, t, method=:zoh)
153153
@test y yreal atol=1e-13
@@ -160,7 +160,7 @@ xreal[3,:,2] = exp.(-t).*(-t .- 1) .+ 1
160160
y, t, x = impulse(systf, t, method=:zoh)
161161
yreal[1,:,1] = exp.(-t)
162162
yreal[2,:,2] = exp.(-t).*(1 .- 2*t)
163-
@test y yreal atol=1e-14
163+
@test y yreal atol=1e-13
164164
#Impulse ss
165165
y, t, x = impulse(1.0sysss, t, method=:zoh)
166166
@test y yreal atol=1e-13

test/test_timeresp.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ xreal[3,:,2] = exp.(-t).*t
7575
y, t, x = step(systf, t0, method=:zoh)
7676
yreal[1,:,1] = 1 .- exp.(-t)
7777
yreal[2,:,2] = -1 .+ exp.(-t) + 2*exp.(-t).*t
78-
@test y yreal atol=1e-14
78+
@test y yreal atol=1e-13
7979
#Step ss
8080
y, t, x = step(sysss, t, method=:zoh)
8181
@test y yreal atol=1e-13
@@ -88,7 +88,7 @@ xreal[3,:,2] = exp.(-t).*(-t .- 1) .+ 1
8888
y, t, x = impulse(systf, t, method=:zoh)
8989
yreal[1,:,1] = exp.(-t)
9090
yreal[2,:,2] = exp.(-t).*(1 .- 2*t)
91-
@test y yreal atol=1e-14
91+
@test y yreal atol=1e-13
9292
#Impulse ss
9393
y, t, x = impulse(1.0sysss, t, method=:zoh)
9494
@test y yreal atol=1e-13

0 commit comments

Comments
 (0)