Skip to content

Commit c680b68

Browse files
olof3olof3
andauthored
WIP: C2d single returnval (#436)
* Basic c2d for systems with delays * single returnval for c2d of StateSpace * Fixes to tests. Co-authored-by: olof3 <[email protected]>
1 parent 6249cba commit c680b68

File tree

8 files changed

+71
-75
lines changed

8 files changed

+71
-75
lines changed

src/ControlSystems.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export LTISystem,
6262
lft,
6363
# Discrete
6464
c2d,
65+
c2d_x0map,
6566
d2c,
6667
# Time Response
6768
step,

src/delay_systems.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function c2d(G::DelayLtiSystem, h::Real, method=:zoh)
6060
error("c2d for DelayLtiSystems only supports zero-order hold")
6161
end
6262
X = append([delayd_ss(τ, h) for τ in G.Tau]...)
63-
Pd = c2d(G.P.P, h)[1]
63+
Pd = c2d(G.P.P, h)
6464
return lft(Pd, X)
6565
end
6666

src/discrete.jl

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,27 @@ export rstd, rstc, dab, c2d_roots2poly, c2d_poly2poly, zpconv#, lsima, indirect_
22

33

44
"""
5-
sysd, x0map = c2d(sys::StateSpace, Ts, method=:zoh)
6-
sysd = c2d(sys::TransferFunction, Ts, method=:zoh)
5+
sysd= c2d(sys::StateSpace, Ts, method=:zoh)
6+
Gd = c2d(G::TransferFunction, Ts, method=:zoh)
77
8-
Convert the continuous system `sys` into a discrete system with sample time
9-
`Ts`, using the provided method. Currently only `:zoh`, `:foh` and `:fwdeuler` are provided. Note that the forward-Euler method generally requires the sample time to be very small in relation to the time-constants of the system.
8+
Convert the continuous-time system `sys` into a discrete-time system with sample time
9+
`Ts`, using the specified `method` (:zoh`, `:foh` or `:fwdeuler`).
10+
Note that the forward-Euler method generally requires the sample time to be very small
11+
relative to the time constants of the system.
1012
11-
Returns the discrete system `sysd`, and for StateSpace systems a matrix `x0map` that transforms the
12-
initial conditions to the discrete domain by
13-
`x0_discrete = x0map*[x0; u0]`"""
14-
function c2d(sys::StateSpace{Continuous}, Ts::Real, method::Symbol=:zoh)
13+
See also `c2d_x0map`
14+
"""
15+
c2d(sys::StateSpace, Ts::Real, method::Symbol=:zoh) = c2d_x0map(sys, Ts, method)[1]
16+
17+
18+
"""
19+
sysd, x0map = c2d_x0map(sys::StateSpace, Ts, method=:zoh)
20+
21+
Returns the discretization `sysd` of the system `sys` and a matrix `x0map` that
22+
transforms the initial conditions to the discrete domain by `x0_discrete = x0map*[x0; u0]`
23+
24+
See `c2d` for further details."""
25+
function c2d_x0map(sys::StateSpace{Continuous}, Ts::Real, method::Symbol=:zoh)
1526
A, B, C, D = ssdata(sys)
1627
T = promote_type(eltype.((A,B,C,D))...)
1728
ny, nu = size(sys)
@@ -220,13 +231,13 @@ function c2d(G::TransferFunction{<:Continuous}, h, args...; kwargs...)
220231
ny, nu = size(G)
221232
@assert (ny + nu == 2) "c2d(G::TransferFunction, h) not implemented for MIMO systems"
222233
sys = ss(G)
223-
sysd = c2d(sys, h, args...; kwargs...)[1]
234+
sysd = c2d(sys, h, args...; kwargs...)
224235
return convert(TransferFunction, sysd)
225236
end
226237

227238
"""
228239
zpc(a,r,b,s)
229-
240+
230241
form conv(a,r) + conv(b,s) where the lengths of the polynomials are equalized by zero-padding such that the addition can be carried out
231242
"""
232243
function zpconv(a,r,b,s)

src/timeresp.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ function lsim(sys::AbstractStateSpace, u::AbstractVecOrMat, t::AbstractVector;
132132
end
133133

134134
if method === :zoh
135-
dsys = c2d(sys, dt, :zoh)[1]
135+
dsys = c2d(sys, dt, :zoh)
136136
elseif method === :foh
137-
dsys, x0map = c2d(sys, dt, :foh)
137+
dsys, x0map = c2d_x0map(sys, dt, :foh)
138138
x0 = x0map*[x0; transpose(u)[:,1]]
139139
else
140140
error("Unsupported discretization method")
@@ -180,7 +180,7 @@ function lsim(sys::AbstractStateSpace, u::Function, t::AbstractVector;
180180

181181
if !iscontinuous(sys) || method === :zoh
182182
if iscontinuous(sys)
183-
dsys = c2d(sys, dt, :zoh)[1]
183+
dsys = c2d(sys, dt, :zoh)
184184
else
185185
if sys.Ts != dt
186186
error("Time vector must match sample time for discrete system")
@@ -224,7 +224,7 @@ If `u` is a function, then `u(x,i)` is called to calculate the control signal ev
224224
LinearAlgebra.promote_op(LinearAlgebra.matprod, eltype(B), eltype(u)))
225225

226226
n = size(u, 1)
227-
227+
228228
# Transposing u allows column-wise operations, which apparently is faster.
229229
ut = transpose(u)
230230

test/framework.jl

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,16 @@ macro test_array_vecs_eps(a, b, tol)
7070
end
7171
end
7272

73-
macro test_c2d(ex, sys_sol, mat_sol, op)
74-
if op == true
75-
quote
76-
sys, mat = $(esc(ex))
77-
@test sys $(esc(sys_sol)) && mat $(esc(mat_sol))
78-
end
79-
else
80-
quote
81-
sys, mat = $(esc(ex))
82-
@test !(sys $(esc(sys_sol))) || !(mat $(esc(mat_sol)))
83-
end
73+
# Currently only works for two return values
74+
macro test_tupleapprox(ex, y1true, y2true)
75+
quote
76+
y1, y2 = $(esc(ex))
77+
@test y1 $(esc(y1true)) && y2 $(esc(y2true))
8478
end
8579
end
8680

8781

8882
approxin(el,col;kwargs...) = any(colel -> isapprox(el, colel; kwargs...), col)
8983
approxsetequal(s1,s2;kwargs...) = all(approxin(p,s1;kwargs...) for p in s2) && all(approxin(p,s2;kwargs...) for p in s1)
84+
85+
dropwhitespace(str) = filter(!isspace, str)

test/test_discrete.jl

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,60 +5,49 @@ C_212 = ss([-5 -3; 2 -9], [1; 2], [1 0; 0 1], [0; 0])
55
C_221 = ss([-5 -3; 2 -9], [1 0; 0 2], [1 0], [0 0])
66
C_222_d = ss([-5 -3; 2 -9], [1 0; 0 2], [1 0; 0 1], [1 0; 0 1])
77

8-
@test c2d(ss(4*[1 0; 0 1]), 0.5, :zoh) == (ss(4*[1 0; 0 1], 0.5), zeros(0, 2))
9-
@test c2d(ss(4*[1 0; 0 1]), 0.5, :foh) == (ss(4*[1 0; 0 1], 0.5), zeros(0, 2))
10-
@test_c2d(c2d(C_111, 0.01, :zoh),
11-
ss([0.951229424500714], [0.019508230199714396], [3], [0], 0.01), [1 0], true)
12-
@test_c2d(c2d(C_111, 0.01, :foh),
8+
@test c2d_x0map(ss(4*[1 0; 0 1]), 0.5, :zoh) == (ss(4*[1 0; 0 1], 0.5), zeros(0, 2))
9+
@test c2d_x0map(ss(4*[1 0; 0 1]), 0.5, :foh) == (ss(4*[1 0; 0 1], 0.5), zeros(0, 2))
10+
@test_tupleapprox(c2d_x0map(C_111, 0.01, :zoh),
11+
ss([0.951229424500714], [0.019508230199714396], [3], [0], 0.01), [1 0])
12+
@test_tupleapprox(c2d_x0map(C_111, 0.01, :foh),
1313
ss([0.951229424500714], [0.01902855227625244], [3], [0.029506188017136226], 0.01),
14-
[1 -0.009835396005712075], true)
15-
@test_c2d(c2d(C_212, 0.01, :zoh),
14+
[1 -0.009835396005712075])
15+
@test_tupleapprox(c2d_x0map(C_212, 0.01, :zoh),
1616
ss([0.9509478368863918 -0.027970882212682433; 0.018647254808454958 0.9136533272694819],
1717
[0.009466805409666932; 0.019219966830212765], [1 0; 0 1], [0; 0], 0.01),
18-
[1 0 0; 0 1 0], true)
19-
@test_c2d(c2d(C_212, 0.01, :foh),
18+
[1 0 0; 0 1 0])
19+
@test_tupleapprox(c2d_x0map(C_212, 0.01, :foh),
2020
ss([0.9509478368863921 -0.027970882212682433; 0.018647254808454954 0.913653327269482],
2121
[0.008957940478201584; 0.018468989584974498], [1 0; 0 1], [0.004820885889482196;
22-
0.009738343195298675], 0.01), [1 0 -0.004820885889482196; 0 1 -0.009738343195298675], true)
23-
@test_c2d(c2d(C_221, 0.01, :zoh),
22+
0.009738343195298675], 0.01), [1 0 -0.004820885889482196; 0 1 -0.009738343195298675])
23+
@test_tupleapprox(c2d_x0map(C_221, 0.01, :zoh),
2424
ss([0.9509478368863918 -0.027970882212682433; 0.018647254808454958 0.9136533272694819],
2525
[0.009753161420545834 -0.0002863560108789034; 9.54520036263011e-5 0.019124514826586465],
26-
[1.0 0.0], [0.0 0.0], 0.01), [1 0 0 0; 0 1 0 0], true)
27-
@test_c2d(c2d(C_221, 0.01, :foh),
26+
[1.0 0.0], [0.0 0.0], 0.01), [1 0 0 0; 0 1 0 0])
27+
@test_tupleapprox(c2d_x0map(C_221, 0.01, :foh),
2828
ss([0.9509478368863921 -0.027970882212682433; 0.018647254808454954 0.913653327269482],
2929
[0.009511049106772921 -0.0005531086285713394; 0.00018436954285711309 0.018284620042117387],
3030
[1 0], [0.004917457305816479 -9.657141633428213e-5], 0.01),
3131
[1 0 -0.004917457305816479 9.657141633428213e-5;
32-
0 1 -3.219047211142736e-5 -0.009706152723187249], true)
33-
@test_c2d(c2d(C_222_d, 0.01, :zoh),
32+
0 1 -3.219047211142736e-5 -0.009706152723187249])
33+
@test_tupleapprox(c2d_x0map(C_222_d, 0.01, :zoh),
3434
ss([0.9509478368863918 -0.027970882212682433; 0.018647254808454958 0.9136533272694819],
3535
[0.009753161420545834 -0.0002863560108789034; 9.54520036263011e-5 0.019124514826586465],
36-
[1 0; 0 1], [1 0; 0 1], 0.01), [1 0 0 0; 0 1 0 0], true)
37-
@test_c2d(c2d(C_222_d, 0.01, :foh),
36+
[1 0; 0 1], [1 0; 0 1], 0.01), [1 0 0 0; 0 1 0 0])
37+
@test_tupleapprox(c2d_x0map(C_222_d, 0.01, :foh),
3838
ss([0.9509478368863921 -0.027970882212682433; 0.018647254808454954 0.913653327269482],
3939
[0.009511049106772921 -0.0005531086285713394; 0.00018436954285711309 0.018284620042117387],
4040
[1 0; 0 1], [1.0049174573058164 -9.657141633428213e-5; 3.219047211142736e-5 1.0097061527231872], 0.01),
41-
[1 0 -0.004917457305816479 9.657141633428213e-5; 0 1 -3.219047211142736e-5 -0.009706152723187249], true)
42-
43-
# Test some false
44-
@test_c2d(c2d(2C_111, 0.01, :zoh), # Factor 2
45-
ss([0.951229424500714], [0.019508230199714396], [3], [0], 0.01), [1 0], false)
46-
@test_c2d(c2d(C_111, 0.01, :foh), #Wrong C
47-
ss([0.951229424500714], [0.01902855227625244], [3*3], [0.029506188017136226], 0.01),
48-
[1 -0.009835396005712075], false)
49-
@test_c2d(c2d(C_212, 0.1, :zoh), # Wrong Ts
50-
ss([0.9509478368863918 -0.027970882212682433; 0.018647254808454958 0.9136533272694819],
51-
[0.009466805409666932; 0.019219966830212765], [1 0; 0 1], [0; 0], 0.01),
52-
[1 0 0; 0 1 0], false)
41+
[1 0 -0.004917457305816479 9.657141633428213e-5; 0 1 -3.219047211142736e-5 -0.009706152723187249])
5342

5443
# Test c2d for transfer functions
5544
G = tf([1, 1], [1, 3, 1])
5645
Gd = c2d(G, 0.2)
5746
@test Gd tf([0, 0.165883310712090, -0.135903621603238], [1.0, -1.518831946985175, 0.548811636094027], 0.2) rtol=1e-14
5847

5948
# Issue #391
60-
@test c2d(tf(C_111), 0.01, :zoh) tf(c2d(C_111, 0.01, :zoh)[1])
61-
@test c2d(tf(C_111), 0.01, :foh) tf(c2d(C_111, 0.01, :foh)[1])
49+
@test c2d(tf(C_111), 0.01, :zoh) tf(c2d(C_111, 0.01, :zoh))
50+
@test c2d(tf(C_111), 0.01, :foh) tf(c2d(C_111, 0.01, :foh))
6251

6352
# c2d on a zpk model should arguably return a zpk model
6453
@test_broken typeof(c2d(zpk(G), 1)) <: TransferFunction{<:ControlSystems.SisoZpk}
@@ -72,27 +61,27 @@ Gd = c2d(G, 0.2)
7261

7362
# d2c
7463
@static if VERSION > v"1.4" # log(matrix) is buggy on previous versions, should be fixed in 1.4 and back-ported to 1.0.6
75-
@test d2c(c2d(C_111, 0.01)[1]) C_111
76-
@test d2c(c2d(C_212, 0.01)[1]) C_212
77-
@test d2c(c2d(C_221, 0.01)[1]) C_221
78-
@test d2c(c2d(C_222_d, 0.01)[1]) C_222_d
64+
@test d2c(c2d(C_111, 0.01)) C_111
65+
@test d2c(c2d(C_212, 0.01)) C_212
66+
@test d2c(c2d(C_221, 0.01)) C_221
67+
@test d2c(c2d(C_222_d, 0.01)) C_222_d
7968
@test d2c(Gd) G
8069

8170
sys = ss([0 1; 0 0], [0;1], [1 0], 0)
82-
sysd = c2d(sys, 1)[1]
71+
sysd = c2d(sys, 1)
8372
@test d2c(sysd) sys
8473
end
8574

8675
# forward euler
87-
@test c2d(C_111, 1, :fwdeuler)[1].A == I + C_111.A
88-
@test d2c(c2d(C_111, 0.01, :fwdeuler)[1], :fwdeuler) C_111
89-
@test d2c(c2d(C_212, 0.01, :fwdeuler)[1], :fwdeuler) C_212
90-
@test d2c(c2d(C_221, 0.01, :fwdeuler)[1], :fwdeuler) C_221
91-
@test d2c(c2d(C_222_d, 0.01, :fwdeuler)[1], :fwdeuler) C_222_d
76+
@test c2d(C_111, 1, :fwdeuler).A == I + C_111.A
77+
@test d2c(c2d(C_111, 0.01, :fwdeuler), :fwdeuler) C_111
78+
@test d2c(c2d(C_212, 0.01, :fwdeuler), :fwdeuler) C_212
79+
@test d2c(c2d(C_221, 0.01, :fwdeuler), :fwdeuler) C_221
80+
@test d2c(c2d(C_222_d, 0.01, :fwdeuler), :fwdeuler) C_222_d
9281
@test d2c(c2d(G, 0.01, :fwdeuler), :fwdeuler) G
9382

9483

95-
Cd = c2d(C_111, 0.001, :fwdeuler)[1]
84+
Cd = c2d(C_111, 0.001, :fwdeuler)
9685
t = 0:0.001:2
9786
y,_ = step(C_111, t)
9887
yd,_ = step(Cd, t)
@@ -130,4 +119,3 @@ p = pole(Gcl)
130119

131120

132121
end
133-

test/test_timeresp.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ y, t, x, uout = lsim(sys,u,t,x0=x0) # Continuous time
1818
th = 1e-6
1919
@test sum(abs.(x[end,:])) < th
2020

21-
y, t, x, uout = lsim(c2d(sys,0.1)[1],u,t,x0=x0) # Discrete time
21+
y, t, x, uout = lsim(c2d(sys,0.1),u,t,x0=x0) # Discrete time
2222
@test sum(abs.(x[end,:])) < th
2323

2424
#Do a manual simulation with uout
@@ -28,7 +28,7 @@ ym, tm, xm = lsim(sys, uout, t, x0=x0)
2828

2929
# Now compare to closed loop
3030
# Discretization is needed before feedback
31-
sysd = c2d(sys, 0.1)[1]
31+
sysd = c2d(sys, 0.1)
3232
# Create the closed loop system
3333
sysdfb = ss(sysd.A-sysd.B*L, sysd.B, sysd.C, sysd.D, 0.1)
3434
#Simulate without input
@@ -90,8 +90,8 @@ yd, td, xd = lsim(sysd, u, t, x0=x0)
9090

9191
# Test lsim with default time vector
9292
uv = randn(length(t))
93-
y,t = lsim(c2d(sys,0.1)[1],uv,t,x0=x0)
94-
yd,td = lsim(c2d(sys,0.1)[1],uv,x0=x0)
93+
y,t = lsim(c2d(sys,0.1),uv,t,x0=x0)
94+
yd,td = lsim(c2d(sys,0.1),uv,x0=x0)
9595
@test yd == y
9696
@test td == t
9797

test/test_transferfunction.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ tf(vecarray(1, 2, [0], [0]), vecarray(1, 2, [1], [1]), 0.005)
105105

106106
# Printing
107107
res = ("TransferFunction{Continuous,ControlSystems.SisoRational{Int64}}\nInput 1 to output 1\ns^2 + 2s + 3\n-------------\ns^2 + 8s + 15\n\nInput 1 to output 2\ns^2 + 2s + 3\n-------------\ns^2 + 8s + 15\n\nInput 2 to output 1\n s + 2\n-------------\ns^2 + 8s + 15\n\nInput 2 to output 2\n s + 2\n-------------\ns^2 + 8s + 15\n\nContinuous-time transfer function model")
108-
@test sprint(show, C_222) == res
108+
@test dropwhitespace(sprint(show, C_222)) == dropwhitespace(res)
109109
res = ("TransferFunction{Discrete{Float64},ControlSystems.SisoRational{Float64}}\nInput 1 to output 1\n1.0z^2 + 2.0z + 3.0\n--------------------\n1.0z^2 - 0.2z - 0.15\n\nInput 1 to output 2\n1.0z^2 + 2.0z + 3.0\n--------------------\n1.0z^2 - 0.2z - 0.15\n\nInput 2 to output 1\n 1.0z + 2.0\n--------------------\n1.0z^2 - 0.2z - 0.15\n\nInput 2 to output 2\n 1.0z + 2.0\n--------------------\n1.0z^2 - 0.2z - 0.15\n\nSample Time: 0.005 (seconds)\nDiscrete-time transfer function model")
110-
@test sprint(show, D_222) == res
110+
@test dropwhitespace(sprint(show, D_222)) == dropwhitespace(res)
111111

112112

113113
@test tf(zpk([1.0 2; 3 4])) == tf([1 2; 3 4])

0 commit comments

Comments
 (0)